Compare commits

..

30 Commits

Author SHA1 Message Date
Alireza Ahmadi bd8d5d6f24 v0.0.2 2024-03-01 18:45:12 +01:00
MisakaNo の 小破站 d39df72896 Optimize for Simplified Chinese translation (#48)
* feat: Optimize for Simplified Chinese translation

* fix: fix language pack index for zhcn
2024-03-01 18:42:27 +01:00
Alireza Ahmadi cfa5f38177 center align tiles contents 2024-03-01 17:23:45 +01:00
Alireza Ahmadi abcf0bca99 using sing-box 1.8.7 2024-03-01 17:18:43 +01:00
Shahin 5de5d6c3d0 Added Simplified Chinese language pack (#47)
* Added Simplified Chinese language pack

* [i18n] completing ch lang

---------

Co-authored-by: jiulingyun <server@jiulingyun.cn>
2024-03-01 15:54:14 +01:00
Alireza Ahmadi 944f671d96 default db location 2024-03-01 14:36:26 +01:00
Alireza Ahmadi 00ee69e541 [cli] add bbr 2024-03-01 13:39:24 +01:00
Alireza Ahmadi f5b609204e tiny fixes 2024-03-01 13:33:15 +01:00
Alireza Ahmadi edc1038623 add/fix releases 2024-03-01 13:09:22 +01:00
Alireza Ahmadi c687a42d7e [cli] add s-ui menu
Co-Authored-By: Shellgate <128194280+shellgate@users.noreply.github.com>
2024-03-01 10:14:24 +01:00
Alireza Ahmadi bf1759ceda [cmd] initiate 2024-02-29 23:28:29 +01:00
Alireza Ahmadi e404342e2c [setting] add webURI 2024-02-29 23:27:32 +01:00
Alireza Ahmadi 4a3917ea5d vless flow only in tls-tcp 2024-02-28 10:33:09 +01:00
Alireza Ahmadi 037dbbd7a0 load html file as template for production 2024-02-27 23:11:55 +01:00
Alireza Ahmadi 541e54c9a4 upgrade to go 1.22 and dependencies 2024-02-27 22:06:07 +01:00
Alireza Ahmadi 2cc78da07f fix alpn 2024-02-27 21:17:00 +01:00
Alireza Ahmadi 63dc779f68 dev mode requirements 2024-02-27 19:55:00 +01:00
Alireza Ahmadi 9ba611649c styling small changes 2024-02-27 19:53:57 +01:00
Alireza Ahmadi b0817e9c05 change credentials #39 2024-02-27 19:53:12 +01:00
Alireza Ahmadi 8a2407fbd3 random name for changable assets 2024-02-27 11:35:50 +01:00
Alireza Ahmadi 204e0d30e1 fix vless inbound flow 2024-02-27 11:34:18 +01:00
Alireza Ahmadi 19a6053098 auto logout 2024-02-27 01:03:00 +01:00
Alireza Ahmadi e91d8038ad change cookies 2024-02-27 01:01:44 +01:00
Alireza Ahmadi 840b7ba1b1 add ws headers host 2024-02-26 21:59:34 +01:00
Alireza Ahmadi 233c27be23 show usage for infinite clients as well #5 2024-02-26 19:09:26 +01:00
Alireza Ahmadi c54d9c15bc add base url 2024-02-26 19:01:01 +01:00
Alireza Ahmadi 30c9ed6aa7 Merge pull request #37 from alireza0/dependabot/github_actions/svenstaro/upload-release-action-2.9.0
Bump svenstaro/upload-release-action from 2.7.0 to 2.9.0
2024-02-22 19:14:09 +01:00
dependabot[bot] 1bdf4e9c4c Bump svenstaro/upload-release-action from 2.7.0 to 2.9.0
Bumps [svenstaro/upload-release-action](https://github.com/svenstaro/upload-release-action) from 2.7.0 to 2.9.0.
- [Release notes](https://github.com/svenstaro/upload-release-action/releases)
- [Changelog](https://github.com/svenstaro/upload-release-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/svenstaro/upload-release-action/compare/2.7.0...2.9.0)

---
updated-dependencies:
- dependency-name: svenstaro/upload-release-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-22 17:03:39 +00:00
Alireza Ahmadi 2f0d7a8502 update readme: uninstall 2024-02-20 17:21:17 +01:00
Alireza Ahmadi 99b8cbc75f Update README default info 2024-02-13 08:35:06 +01:00
46 changed files with 2034 additions and 167 deletions
+26 -15
View File
@@ -10,16 +10,21 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
platform: [amd64, arm64, arm] platform:
- amd64
- arm64
- armv7
- 386
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5.0.0 uses: actions/setup-go@v5
with: with:
go-version: '1.21' cache: false
go-version: '1.22'
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@@ -27,13 +32,15 @@ jobs:
node-version: '20' node-version: '20'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- name: Install dependencies for arm64 and arm - name: Install dependencies
if: matrix.platform == 'arm64' || matrix.platform == 'arm'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt install gcc-aarch64-linux-gnu if [ "${{ matrix.platform }}" == "arm64" ]; then
if [ "${{ matrix.platform }}" == "arm" ]; then sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
fi fi
- name: Build frontend - name: Build frontend
@@ -44,26 +51,30 @@ jobs:
cd .. cd ..
mv frontend/dist backend/web/html mv frontend/dist backend/web/html
- name: Set evironments - name: Build s-ui & singbox
run: | run: |
export CGO_ENABLED=1 export CGO_ENABLED=1
export GOOS=linux export GOOS=linux
export GOARCH=${{ matrix.platform }} export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "arm" ]; then elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
fi fi
- name: Build sing-box #### Build Sing-Box
run: | git clone -b v1.8.7 https://github.com/SagerNet/sing-box
git clone -b v1.8.5 https://github.com/SagerNet/sing-box
cd sing-box cd sing-box
go build -tags with_v2ray_api,with_clash_api,with_grpc,with_quic,with_ech -o sing-box ./cmd/sing-box go build -tags with_v2ray_api,with_clash_api,with_grpc,with_quic,with_ech -o sing-box ./cmd/sing-box
cd .. cd ..
- name: Build s-ui ### Build s-ui
run: |
cd backend cd backend
go build -o ../sui main.go go build -o ../sui main.go
cd .. cd ..
@@ -80,7 +91,7 @@ jobs:
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
- name: Upload - name: Upload
uses: svenstaro/upload-release-action@2.7.0 uses: svenstaro/upload-release-action@v2
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }} tag: ${{ github.ref }}
+1 -1
View File
@@ -3,7 +3,7 @@ WORKDIR /app
COPY frontend/ ./ COPY frontend/ ./
RUN npm install && npm run build RUN npm install && npm run build
FROM golang:1.21-alpine AS backend-builder FROM golang:1.22-alpine AS backend-builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
+18
View File
@@ -26,6 +26,11 @@
| Dark/Light Theme | :heavy_check_mark: | | Dark/Light Theme | :heavy_check_mark: |
## Default Installation Informarion
- Panel Port: 2095
- Subscription Port: 2096
- User/Passowrd: admin
## Install & Upgrade to Latest Version ## Install & Upgrade to Latest Version
```sh ```sh
@@ -40,6 +45,19 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.s
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh) 0.0.1 bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh) 0.0.1
``` ```
## Uninstall S-UI
```sh
systemctl disable sing-box --now
systemctl disable s-ui --now
rm -f /etc/systemd/system/s-ui.service
rm -f /etc/systemd/system/sing-box.service
systemctl daemon-reload
rm -fr /usr/local/s-ui
```
## Install using Docker ## Install using Docker
<details> <details>
+27 -2
View File
@@ -27,7 +27,8 @@ func NewAPIHandler(g *gin.RouterGroup) {
func (a *APIHandler) initRouter(g *gin.RouterGroup) { func (a *APIHandler) initRouter(g *gin.RouterGroup) {
g.Use(func(c *gin.Context) { g.Use(func(c *gin.Context) {
if c.Request.URL.Path != "/api/login" && c.Request.URL.Path != "/api/logout" { path := c.Request.URL.Path
if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") {
checkLogin(c) checkLogin(c)
} }
}) })
@@ -62,9 +63,26 @@ func (a *APIHandler) postHandler(c *gin.Context) {
} }
err = SetLoginUser(c, loginUser) err = SetLoginUser(c, loginUser)
logger.Info("user ", loginUser, " login success") if err == nil {
logger.Info("user ", loginUser, " login success")
} else {
logger.Warning("login failed: ", err)
}
jsonMsg(c, "", nil) jsonMsg(c, "", nil)
case "changePass":
id := c.Request.FormValue("id")
oldPass := c.Request.FormValue("oldPass")
newUsername := c.Request.FormValue("newUsername")
newPass := c.Request.FormValue("newPass")
err = a.UserService.ChangePass(id, oldPass, newUsername, newPass)
if err == nil {
logger.Info("change user credentials success")
jsonMsg(c, "save", nil)
} else {
logger.Warning("change user credentials failed:", err)
jsonMsg(c, "", err)
}
case "save": case "save":
loginUser := GetLoginUser(c) loginUser := GetLoginUser(c)
data := map[string]string{} data := map[string]string{}
@@ -99,6 +117,13 @@ func (a *APIHandler) getHandler(c *gin.Context) {
return return
} }
jsonObj(c, data, nil) jsonObj(c, data, nil)
case "users":
users, err := a.UserService.GetUsers()
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, *users, nil)
case "setting": case "setting":
data, err := a.SettingService.GetAllSetting() data, err := a.SettingService.GetAllSetting()
if err != nil { if err != nil {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"encoding/gob" "encoding/gob"
"s-ui/database/model" "s-ui/database/model"
"github.com/gin-contrib/sessions" sessions "github.com/Calidity/gin-sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
+1 -1
View File
@@ -69,7 +69,7 @@ func pureJsonMsg(c *gin.Context, success bool, msg string) {
func checkLogin(c *gin.Context) { func checkLogin(c *gin.Context) {
if !IsLogin(c) { if !IsLogin(c) {
if c.GetHeader("X-Requested-With") == "XMLHttpRequest" { if c.GetHeader("X-Requested-With") == "XMLHttpRequest" {
pureJsonMsg(c, false, "Not authorized") pureJsonMsg(c, false, "Invalid login")
} else { } else {
c.Redirect(http.StatusTemporaryRedirect, "/login") c.Redirect(http.StatusTemporaryRedirect, "/login")
} }
+63
View File
@@ -0,0 +1,63 @@
package cmd
import (
"fmt"
"s-ui/config"
"s-ui/database"
"s-ui/service"
)
func resetAdmin() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
userService := service.UserService{}
err = userService.UpdateFirstUser("admin", "admin")
if err != nil {
fmt.Println("reset admin credentials failed:", err)
} else {
fmt.Println("reset admin credentials success")
}
}
func updateAdmin(username string, password string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
if username != "" || password != "" {
userService := service.UserService{}
err := userService.UpdateFirstUser(username, password)
if err != nil {
fmt.Println("reset admin credentials failed:", err)
} else {
fmt.Println("reset admin credentials success")
}
}
}
func showAdmin() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
userService := service.UserService{}
userModel, err := userService.GetFirstUser()
if err != nil {
fmt.Println("get current user info failed,error info:", err)
}
username := userModel.Username
userpasswd := userModel.Password
if (username == "") || (userpasswd == "") {
fmt.Println("current username or password is empty")
}
fmt.Println("First admin credentials:")
fmt.Println("\tUsername:\t", username)
fmt.Println("\tPassword:\t", userpasswd)
}
+92
View File
@@ -0,0 +1,92 @@
package cmd
import (
"flag"
"fmt"
"os"
"s-ui/config"
)
func ParseCmd() {
var showVersion bool
flag.BoolVar(&showVersion, "v", false, "show version")
adminCmd := flag.NewFlagSet("admin", flag.ExitOnError)
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
var username string
var password string
var port int
var path string
var subPort int
var subPath string
var reset bool
var show bool
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
settingCmd.BoolVar(&show, "show", false, "show current settings")
settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&path, "path", "", "set panel path")
settingCmd.IntVar(&subPort, "subPort", 0, "set sub port")
settingCmd.StringVar(&subPath, "subPath", "", "set sub path")
adminCmd.BoolVar(&show, "show", false, "show first admin credentials")
adminCmd.BoolVar(&reset, "reset", false, "reset first admin credentials")
adminCmd.StringVar(&username, "username", "", "set login username")
adminCmd.StringVar(&password, "password", "", "set login password")
oldUsage := flag.Usage
flag.Usage = func() {
oldUsage()
fmt.Println()
fmt.Println("Commands:")
fmt.Println(" admin set/reset/show first admin credentials")
fmt.Println(" setting set/reset/show settings")
fmt.Println()
adminCmd.Usage()
fmt.Println()
settingCmd.Usage()
}
flag.Parse()
if showVersion {
fmt.Println(config.GetVersion())
return
}
switch os.Args[1] {
case "admin":
err := adminCmd.Parse(os.Args[2:])
if err != nil {
fmt.Println(err)
return
}
switch {
case show:
showAdmin()
case reset:
resetAdmin()
default:
updateAdmin(username, password)
showAdmin()
}
case "setting":
err := settingCmd.Parse(os.Args[2:])
if err != nil {
fmt.Println(err)
return
}
switch {
case show:
showSetting()
case reset:
resetSetting()
default:
updateSetting(port, path, subPort, subPath)
showSetting()
}
default:
fmt.Println("Invalid subcommands")
flag.Usage()
}
}
+105
View File
@@ -0,0 +1,105 @@
package cmd
import (
"fmt"
"s-ui/config"
"s-ui/database"
"s-ui/service"
)
func resetSetting() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
settingService := service.SettingService{}
err = settingService.ResetSettings()
if err != nil {
fmt.Println("reset setting failed:", err)
} else {
fmt.Println("reset setting success")
}
}
func updateSetting(port int, path string, subPort int, subPath string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
settingService := service.SettingService{}
if port > 0 {
err := settingService.SetPort(port)
if err != nil {
fmt.Println("set port failed:", err)
} else {
fmt.Println("set port success")
}
}
if path != "" {
err := settingService.SetWebPath(path)
if err != nil {
fmt.Println("set path failed:", err)
} else {
fmt.Println("set path success")
}
}
if subPort > 0 {
err := settingService.SetSubPort(subPort)
if err != nil {
fmt.Println("set sub port failed:", err)
} else {
fmt.Println("set sub port success")
}
}
if subPath != "" {
err := settingService.SetSubPath(subPath)
if err != nil {
fmt.Println("set sub path failed:", err)
} else {
fmt.Println("set sub path success")
}
}
}
func showSetting() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
settingService := service.SettingService{}
allSetting, err := settingService.GetAllSetting()
if err != nil {
fmt.Println("get current port failed,error info:", err)
}
fmt.Println("Current panel settings:")
fmt.Println("\tPanel port:\t", (*allSetting)["webPort"])
fmt.Println("\tPanel path:\t", (*allSetting)["webPath"])
if (*allSetting)["webListen"] != "" {
fmt.Println("\tPanel IP:\t", (*allSetting)["webListen"])
}
if (*allSetting)["webDomain"] != "" {
fmt.Println("\tPanel Domain:\t", (*allSetting)["webDomain"])
}
if (*allSetting)["webURI"] != "" {
fmt.Println("\tPanel URI:\t", (*allSetting)["webURI"])
}
fmt.Println()
fmt.Println("Current subscription settings:")
fmt.Println("\tSub port:\t", (*allSetting)["subPort"])
fmt.Println("\tSub path:\t", (*allSetting)["subPath"])
if (*allSetting)["subListen"] != "" {
fmt.Println("\tSub IP:\t", (*allSetting)["subListen"])
}
if (*allSetting)["subDomain"] != "" {
fmt.Println("\tSub Domain:\t", (*allSetting)["subDomain"])
}
if (*allSetting)["subURI"] != "" {
fmt.Println("\tSub URI:\t", (*allSetting)["subURI"])
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ func GetBinFolderPath() string {
func GetDBFolderPath() string { func GetDBFolderPath() string {
dbFolderPath := os.Getenv("SUI_DB_FOLDER") dbFolderPath := os.Getenv("SUI_DB_FOLDER")
if dbFolderPath == "" { if dbFolderPath == "" {
dbFolderPath = "db" dbFolderPath = "/usr/local/s-ui/db"
} }
return dbFolderPath return dbFolderPath
} }
+1 -1
View File
@@ -1 +1 @@
0.0.1 0.0.2
+17 -20
View File
@@ -1,23 +1,23 @@
module s-ui module s-ui
go 1.21.5 go 1.22.0
require ( require (
github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/sessions v0.0.5
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/v2fly/v2ray-core/v5 v5.13.0
gorm.io/driver/sqlite v1.5.5 gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.7 gorm.io/gorm v1.25.7
) )
require ( require (
github.com/adrg/xdg v0.4.0 // indirect github.com/adrg/xdg v0.4.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect github.com/bytedance/sonic v1.11.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
@@ -26,42 +26,39 @@ require (
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect github.com/gorilla/sessions v1.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.7.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.18.0 // indirect golang.org/x/crypto v0.20.0 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.21.0 // indirect
golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.17.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
google.golang.org/protobuf v1.32.0 // indirect google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
require ( require (
github.com/Calidity/gin-sessions v1.3.1
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/validator/v10 v10.17.0 // indirect github.com/go-playground/validator/v10 v10.18.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.24.1 github.com/shirou/gopsutil/v3 v3.24.1
github.com/v2fly/v2ray-core/v5 v5.13.0 google.golang.org/grpc v1.62.0
google.golang.org/grpc v1.61.0
) )
+36 -27
View File
@@ -1,3 +1,5 @@
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY= github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY=
@@ -12,8 +14,8 @@ github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.11.1 h1:JC0+6c9FoWYYxakaoa+c5QTtJeiSZNeByOBhXtAFSn4=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
@@ -37,8 +39,6 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
@@ -48,8 +48,9 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@@ -60,8 +61,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -105,8 +106,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU= github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
@@ -121,8 +122,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -165,8 +167,9 @@ github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw= github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
@@ -188,6 +191,7 @@ github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -200,10 +204,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
@@ -224,8 +230,9 @@ github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2Rer
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ= github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY= github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8= go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
@@ -236,15 +243,15 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -254,12 +261,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -268,13 +277,13 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+11 -1
View File
@@ -5,10 +5,11 @@ import (
"os" "os"
"os/signal" "os/signal"
"s-ui/app" "s-ui/app"
"s-ui/cmd"
"syscall" "syscall"
) )
func main() { func runApp() {
app := app.NewApp() app := app.NewApp()
err := app.Init() err := app.Init()
@@ -36,3 +37,12 @@ func main() {
} }
} }
} }
func main() {
if len(os.Args) < 2 {
runApp()
return
} else {
cmd.ParseCmd()
}
}
+56
View File
@@ -21,6 +21,8 @@ var defaultValueMap = map[string]string{
"webSecret": common.Random(32), "webSecret": common.Random(32),
"webCertFile": "", "webCertFile": "",
"webKeyFile": "", "webKeyFile": "",
"webPath": "/app/",
"webURI": "",
"sessionMaxAge": "0", "sessionMaxAge": "0",
"timeLocation": "Asia/Tehran", "timeLocation": "Asia/Tehran",
"subListen": "", "subListen": "",
@@ -67,6 +69,11 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
return &allSetting, nil return &allSetting, nil
} }
func (s *SettingService) ResetSettings() error {
db := database.GetDB()
return db.Where("1 = 1").Delete(model.Setting{}).Error
}
func (s *SettingService) getSetting(key string) (*model.Setting, error) { func (s *SettingService) getSetting(key string) (*model.Setting, error) {
db := database.GetDB() db := database.GetDB()
setting := &model.Setting{} setting := &model.Setting{}
@@ -158,6 +165,30 @@ func (s *SettingService) GetKeyFile() (string, error) {
return s.getString("webKeyFile") return s.getString("webKeyFile")
} }
func (s *SettingService) GetWebPath() (string, error) {
webPath, err := s.getString("webPath")
if err != nil {
return "", err
}
if !strings.HasPrefix(webPath, "/") {
webPath = "/" + webPath
}
if !strings.HasSuffix(webPath, "/") {
webPath += "/"
}
return webPath, nil
}
func (s *SettingService) SetWebPath(webPath string) error {
if !strings.HasPrefix(webPath, "/") {
webPath = "/" + webPath
}
if !strings.HasSuffix(webPath, "/") {
webPath += "/"
}
return s.setString("webPath", webPath)
}
func (s *SettingService) GetSecret() ([]byte, error) { func (s *SettingService) GetSecret() ([]byte, error) {
secret, err := s.getString("webSecret") secret, err := s.getString("webSecret")
if secret == defaultValueMap["webSecret"] { if secret == defaultValueMap["webSecret"] {
@@ -195,6 +226,10 @@ func (s *SettingService) GetSubPort() (int, error) {
return s.getInt("subPort") return s.getInt("subPort")
} }
func (s *SettingService) SetSubPort(subPort int) error {
return s.setInt("subPort", subPort)
}
func (s *SettingService) GetSubPath() (string, error) { func (s *SettingService) GetSubPath() (string, error) {
subPath, err := s.getString("subPath") subPath, err := s.getString("subPath")
if err != nil { if err != nil {
@@ -209,6 +244,16 @@ func (s *SettingService) GetSubPath() (string, error) {
return subPath, nil return subPath, nil
} }
func (s *SettingService) SetSubPath(subPath string) error {
if !strings.HasPrefix(subPath, "/") {
subPath = "/" + subPath
}
if !strings.HasSuffix(subPath, "/") {
subPath += "/"
}
return s.setString("subPath", subPath)
}
func (s *SettingService) GetSubDomain() (string, error) { func (s *SettingService) GetSubDomain() (string, error) {
return s.getString("subDomain") return s.getString("subDomain")
} }
@@ -278,6 +323,17 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
} }
} }
// Correct Pathes start and ends with `/`
if key == "webPath" ||
key == "subPath" {
if !strings.HasPrefix(obj, "/") {
obj = "/" + obj
}
if !strings.HasSuffix(obj, "/") {
obj += "/"
}
}
err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error
if err != nil { if err != nil {
return err return err
+57 -1
View File
@@ -13,6 +13,40 @@ import (
type UserService struct { type UserService struct {
} }
func (s *UserService) GetFirstUser() (*model.User, error) {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).
First(user).
Error
if err != nil {
return nil, err
}
return user, nil
}
func (s *UserService) UpdateFirstUser(username string, password string) error {
if username == "" {
return common.NewError("username can not be empty")
} else if password == "" {
return common.NewError("password can not be empty")
}
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).First(user).Error
if database.IsNotFound(err) {
user.Username = username
user.Password = password
return db.Model(model.User{}).Create(user).Error
} else if err != nil {
return err
}
user.Username = username
user.Password = password
return db.Save(user).Error
}
func (s *UserService) Login(username string, password string, remoteIP string) (string, error) { func (s *UserService) Login(username string, password string, remoteIP string) (string, error) {
user := s.CheckUser(username, password, remoteIP) user := s.CheckUser(username, password, remoteIP)
if user == nil { if user == nil {
@@ -36,7 +70,7 @@ func (s *UserService) CheckUser(username string, password string, remoteIP strin
return nil return nil
} }
lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + "-" + remoteIP lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + " " + remoteIP
err = db.Model(model.User{}). err = db.Model(model.User{}).
Where("username = ?", username). Where("username = ?", username).
Update("last_logins", &lastLoginTxt).Error Update("last_logins", &lastLoginTxt).Error
@@ -45,3 +79,25 @@ func (s *UserService) CheckUser(username string, password string, remoteIP strin
} }
return user return user
} }
func (s *UserService) GetUsers() (*[]model.User, error) {
var users []model.User
db := database.GetDB()
err := db.Model(model.User{}).Select("id,username,last_logins").Scan(&users).Error
if err != nil {
return nil, err
}
return &users, nil
}
func (s *UserService) ChangePass(id string, oldPass string, newUser string, newPass string) error {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).Where("id = ? AND password = ?", id, oldPass).First(user).Error
if err != nil || database.IsNotFound(err) {
return err
}
user.Username = newUser
user.Password = newPass
return db.Save(user).Error
}
+31 -13
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"embed" "embed"
"html/template"
"io" "io"
"io/fs" "io/fs"
"net" "net"
@@ -17,9 +18,9 @@ import (
"strconv" "strconv"
"strings" "strings"
sessions "github.com/Calidity/gin-sessions"
"github.com/Calidity/gin-sessions/cookie"
"github.com/gin-contrib/gzip" "github.com/gin-contrib/gzip"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -53,6 +54,19 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default() engine := gin.Default()
// Load the HTML template
t := template.New("").Funcs(engine.FuncMap)
template, err := t.ParseFS(content, "html/index.html")
if err != nil {
return nil, err
}
engine.SetHTMLTemplate(template)
base_url, err := s.settingService.GetWebPath()
if err != nil {
return nil, err
}
webDomain, err := s.settingService.GetWebDomain() webDomain, err := s.settingService.GetWebDomain()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -68,10 +82,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
} }
engine.Use(gzip.Gzip(gzip.DefaultCompression)) engine.Use(gzip.Gzip(gzip.DefaultCompression))
assetsBasePath := "/assets/" assetsBasePath := base_url + "assets/"
store := cookie.NewStore(secret) store := cookie.NewStore(secret)
engine.Use(sessions.Sessions("session", store)) engine.Use(sessions.Sessions("s-ui", store))
engine.Use(func(c *gin.Context) { engine.Use(func(c *gin.Context) {
uri := c.Request.RequestURI uri := c.Request.RequestURI
if strings.HasPrefix(uri, assetsBasePath) { if strings.HasPrefix(uri, assetsBasePath) {
@@ -87,26 +102,29 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine.StaticFS(assetsBasePath, http.FS(assetsFS)) engine.StaticFS(assetsBasePath, http.FS(assetsFS))
group_api := engine.Group("/api") group_api := engine.Group(base_url + "api")
api.NewAPIHandler(group_api) api.NewAPIHandler(group_api)
// Serve index.html as the entry point // Serve index.html as the entry point
// Handle all other routes by serving index.html // Handle all other routes by serving index.html
engine.NoRoute(func(c *gin.Context) { engine.NoRoute(func(c *gin.Context) {
if c.Request.URL.Path != "/login" && !api.IsLogin(c) { if c.Request.URL.Path == strings.TrimSuffix(base_url, "/") {
c.Redirect(http.StatusTemporaryRedirect, "/login") c.Redirect(http.StatusTemporaryRedirect, base_url)
return return
} }
if c.Request.URL.Path == "/login" && api.IsLogin(c) { if !strings.HasPrefix(c.Request.URL.Path, base_url) {
c.Redirect(http.StatusTemporaryRedirect, "/") c.String(404, "")
return return
} }
data, err := content.ReadFile("html/index.html") if c.Request.URL.Path != base_url+"login" && !api.IsLogin(c) {
if err != nil { c.Redirect(http.StatusTemporaryRedirect, base_url+"login")
c.String(http.StatusInternalServerError, "Internal Server Error")
return return
} }
c.Data(http.StatusOK, "text/html", data) if c.Request.URL.Path == base_url+"login" && api.IsLogin(c) {
c.Redirect(http.StatusTemporaryRedirect, base_url)
return
}
c.HTML(http.StatusOK, "index.html", gin.H{"BASE_URL": base_url})
}) })
return engine, nil return engine, nil
+6
View File
@@ -5,6 +5,12 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="assets/favicon.ico" /> <link rel="icon" href="assets/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script>
window.BASE_URL = "{{ .BASE_URL }}"
// Dev Mode
if (window.BASE_URL.charAt(0) === '{') window.BASE_URL = "/app/"
</script>
<title>S-UI</title> <title>S-UI</title>
</head> </head>
+3 -3
View File
@@ -143,9 +143,9 @@ export default {
usePath: 0, usePath: 0,
defaults: defaultInTls, defaults: defaultInTls,
alpn: [ alpn: [
{ title: "H3", value: 'HTTP/3' }, { title: "H3", value: 'h3' },
{ title: "H2", value: 'HTTP/2' }, { title: "H2", value: 'h2' },
{ title: "Http1.1", value: 'HTTP/1.1' }, { title: "Http/1.1", value: 'http/1.1' },
], ],
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ], tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
cipher_suites: [ cipher_suites: [
+2 -2
View File
@@ -47,7 +47,7 @@
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i"> <v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
<v-card class="rounded-lg" variant="outlined" height="200px" <v-card class="rounded-lg" variant="outlined" height="200px"
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title"> :title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
<v-card-text style="padding: 0 16px;"> <v-card-text style="padding: 0 16px;" align="center" justify="center">
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" /> <Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
<History :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'h'" /> <History :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'h'" />
<template v-if="i == 'i-sys'"> <template v-if="i == 'i-sys'">
@@ -184,7 +184,7 @@ const reloadItems = computed({
const reloadData = async () => { const reloadData = async () => {
const request = [...new Set(reloadItems.value.map(r => r.split('-')[1]))] const request = [...new Set(reloadItems.value.map(r => r.split('-')[1]))]
const data = await HttpUtils.get('/api/status',{ r: request.join(',')}) const data = await HttpUtils.get('api/status',{ r: request.join(',')})
if (data.success) { if (data.success) {
tilesData.value = data.obj tilesData.value = data.obj
} }
@@ -7,6 +7,15 @@
v-model="transport.path"> v-model="transport.path">
</v-text-field> </v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field
:label="$t('transport.host')"
hide-details
v-model="host">
</v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6" md="4"> <v-col cols="12" sm="6" md="4">
<v-text-field <v-text-field
label="Max Early Data" label="Max Early Data"
@@ -42,10 +51,16 @@ export default {
get() { return this.WS.max_early_data ? this.WS.max_early_data : '' }, get() { return this.WS.max_early_data ? this.WS.max_early_data : '' },
set(newValue:number) { this.$props.transport.max_early_data = newValue != 0 ? newValue : undefined } set(newValue:number) { this.$props.transport.max_early_data = newValue != 0 ? newValue : undefined }
}, },
host: {
get() { return this.WS.headers?.Host ? this.WS.headers.Host : '' },
set(newValue:string) {
this.$props.transport.headers = newValue != "" ? { Host: newValue } : undefined
}
},
}, },
mounted() { mounted() {
this.WS.early_data_header_name = 'Sec-WebSocket-Protocol' this.WS.early_data_header_name ??= 'Sec-WebSocket-Protocol'
this.WS.path = '/' this.WS.path ??= '/'
} }
} }
</script> </script>
+1
View File
@@ -1,6 +1,7 @@
<template> <template>
<v-app-bar :elevation="5"> <v-app-bar :elevation="5">
<v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" /> <v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" />
<span v-else style="width: 24px"></span>
<v-app-bar-title :text="$t(<string>$router.currentRoute.value.name)" class="align-center text-center " /> <v-app-bar-title :text="$t(<string>$router.currentRoute.value.name)" class="align-center text-center " />
<v-btn prepend-icon="mdi-content-save" v-if="stateChange" :text="$t('actions.save')" @click="saveChanges"></v-btn> <v-btn prepend-icon="mdi-content-save" v-if="stateChange" :text="$t('actions.save')" @click="saveChanges"></v-btn>
<v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon> <v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon>
+7
View File
@@ -25,3 +25,10 @@ const isMobile = computed( ():boolean =>{
return smAndDown.value return smAndDown.value
}) })
</script> </script>
<style>
.v-card-subtitle {
text-align: center;
border-bottom: 1px solid gray;
}
</style>
+5 -7
View File
@@ -32,7 +32,7 @@
</v-list-item> </v-list-item>
</v-list> </v-list>
<template v-slot:append> <template v-slot:append>
<v-list-item prepend-icon="mdi-logout" :title="$t('menu.logout')" @click="logout"></v-list-item> <v-list-item prepend-icon="mdi-logout" :title="$t('menu.logout')" @click="Logout"></v-list-item>
</template> </template>
</v-navigation-drawer> </v-navigation-drawer>
</template> </template>
@@ -40,7 +40,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import router from '@/router' import router from '@/router'
import HttpUtil from '@/plugins/httputil' import { logout } from '@/plugins/httputil'
const props = defineProps(['isMobile','displayDrawer']) const props = defineProps(['isMobile','displayDrawer'])
@@ -55,13 +55,11 @@ const menu = [
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' }, { title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' }, { title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' }, { title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
{ title: 'pages.admins', icon: 'mdi-account-tie', path: '/admins' },
{ title: 'pages.settings', icon: 'mdi-cog', path: '/settings' }, { title: 'pages.settings', icon: 'mdi-cog', path: '/settings' },
] ]
const logout = async () => { const Logout = async () => {
const response = await HttpUtil.get('/api/logout') logout()
if(response.success){
router.push('/login')
}
} }
</script> </script>
+96
View File
@@ -0,0 +1,96 @@
<template>
<v-dialog transition="dialog-bottom-transition" width="400">
<v-card class="rounded-lg">
<v-card-title>
{{ $t('admin.changeCred') + " " + user.username }}
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-row>
<v-col>
<v-text-field v-model="newData.oldPass" :label="$t('admin.oldPass')" :rules="passwordRules" type="password" required></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field v-model="newData.newUsername" :label="$t('admin.newUname')" :rules="usernameRules" required></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field v-model="newData.newPass" :label="$t('admin.newPass')" :rules="passwordRules" type="password" required></v-text-field>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="blue-darken-1"
variant="outlined"
@click="closeModal"
>
{{ $t('actions.close') }}
</v-btn>
<v-btn
color="blue-darken-1"
variant="tonal"
@click="saveChanges"
>
{{ $t('actions.save') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import { i18n } from '@/locales'
export default {
props: ['visible', 'user'],
data() {
return {
newData: {
id: 0,
oldPass: "",
newUsername: "",
newPass: ""
},
usernameRules: [
(value: string) => {
if (value?.length > 0) return true
return i18n.global.t('login.unRules')
},
],
passwordRules: [
(value: string) => {
if (value?.length > 0) return true
return i18n.global.t('login.pwRules')
},
]
}
},
methods: {
resetData() {
this.newData.id = this.$props.user.id
this.newData.oldPass = ""
this.newData.newUsername = ""
this.newData.newPass = ""
},
closeModal() {
this.resetData() // reset
this.$emit('close')
},
saveChanges() {
if (this.newData.oldPass == '' || this.newData.newUsername == '' || this.newData.newPass == '') return
this.$emit('save', this.newData)
},
},
watch: {
visible(newValue) { if (newValue) {
this.resetData()
}
},
},
}
</script>
+1 -1
View File
@@ -101,7 +101,7 @@ export default {
methods: { methods: {
async loadData(limit: number) { async loadData(limit: number) {
this.loading = true this.loading = true
const data = await HttpUtils.get('/api/stats', { resource: this.resource, tag: this.tag, limit: limit }) const data = await HttpUtils.get('api/stats', { resource: this.resource, tag: this.tag, limit: limit })
if (data.success && data.obj) { if (data.success && data.obj) {
const obj = <any[]>data.obj const obj = <any[]>data.obj
const l = String(i18n.global.locale) == 'fa' ? "fa-IR" : "en-US" const l = String(i18n.global.locale) == 'fa' ? "fa-IR" : "en-US"
+12
View File
@@ -17,6 +17,7 @@ export default {
network: "Network", network: "Network",
copyToClipboard: "Copy to clipboard", copyToClipboard: "Copy to clipboard",
noData: "No data!", noData: "No data!",
invalidLogin: "Invalid Login!",
online: "Online", online: "Online",
pages: { pages: {
login: "Login", login: "Login",
@@ -26,6 +27,7 @@ export default {
clients: "Clients", clients: "Clients",
rules: "Rules", rules: "Rules",
basics: "Basics", basics: "Basics",
admins: "Admins",
settings: "Settings", settings: "Settings",
}, },
main: { main: {
@@ -69,10 +71,12 @@ export default {
del: "Delete", del: "Delete",
save: "Save", save: "Save",
update: "Update", update: "Update",
submit: "Submit",
close: "Close", close: "Close",
restartApp: "Restart App", restartApp: "Restart App",
}, },
login: { login: {
title: "Login",
username: "Username", username: "Username",
unRules: "Username can not be empty", unRules: "Username can not be empty",
password: "Password", password: "Password",
@@ -81,14 +85,22 @@ export default {
menu: { menu: {
logout: "Logout", logout: "Logout",
}, },
admin: {
changeCred: "Change credentials",
oldPass: "Current Password",
newUname: "New Username",
newPass: "New Password",
},
setting: { setting: {
interface: "Interface", interface: "Interface",
sub: "Subscription", sub: "Subscription",
addr: "Address", addr: "Address",
port: "Port", port: "Port",
webPath: "Base URI",
domain: "Domain", domain: "Domain",
sslKey: "SSL Key Path", sslKey: "SSL Key Path",
sslCert: "SSL Certificate Path", sslCert: "SSL Certificate Path",
webUri: "Panel URI",
sessionAge: "Session Maximum Age", sessionAge: "Session Maximum Age",
timeLoc: "Timezone Location", timeLoc: "Timezone Location",
subEncode: "Enable Encoding", subEncode: "Enable Encoding",
+12
View File
@@ -17,6 +17,7 @@ export default {
network: "شبکه", network: "شبکه",
copyToClipboard: "کپی در حافظه", copyToClipboard: "کپی در حافظه",
noData: "بدون داده!", noData: "بدون داده!",
invalidLogin: "ورود نامعتبر!",
online: "آنلاین", online: "آنلاین",
pages: { pages: {
login: "ورود", login: "ورود",
@@ -26,6 +27,7 @@ export default {
clients: "کاربران", clients: "کاربران",
rules: "قوانین", rules: "قوانین",
basics: "ترازها", basics: "ترازها",
admins: "ادمین‌ها",
settings: "پیکربندی", settings: "پیکربندی",
}, },
main: { main: {
@@ -69,10 +71,12 @@ export default {
del: "حذف", del: "حذف",
save: "ذخیره", save: "ذخیره",
update: "بروزرسانی", update: "بروزرسانی",
submit: "ارسال",
close: "بستن", close: "بستن",
restartApp: "ریستارت پنل", restartApp: "ریستارت پنل",
}, },
login: { login: {
title: "ورود",
username: "نام کاربری", username: "نام کاربری",
unRules: "نام کاربری نمی‌تواند خالی باشد", unRules: "نام کاربری نمی‌تواند خالی باشد",
password: "کلمه عبور", password: "کلمه عبور",
@@ -81,14 +85,22 @@ export default {
menu: { menu: {
logout: "خروج", logout: "خروج",
}, },
admin: {
changeCred: "ویرایش داده‌ها",
oldPass: "رمز کنونی",
newUname: "نام کاربری جدید",
newPass: "رمز جدید",
},
setting: { setting: {
interface: "نما", interface: "نما",
sub: "سابسکریپشن", sub: "سابسکریپشن",
addr: "آدرس", addr: "آدرس",
port: "پورت", port: "پورت",
webPath: "مسیر پایه",
domain: "دامنه", domain: "دامنه",
sslKey: "مسیر فایل کلید", sslKey: "مسیر فایل کلید",
sslCert: "مسیر فایل گواهی", sslCert: "مسیر فایل گواهی",
webUri: "آدرس نهایی پنل",
sessionAge: "بیشینه زمان لاگین ماندن", sessionAge: "بیشینه زمان لاگین ماندن",
timeLoc: "منطقه زمانی", timeLoc: "منطقه زمانی",
subEncode: "رمزگذاری", subEncode: "رمزگذاری",
+3
View File
@@ -1,6 +1,7 @@
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
import en from './en' import en from './en'
import fa from './fa' import fa from './fa'
import zhcn from './zhcn'
export const i18n = createI18n({ export const i18n = createI18n({
@@ -10,10 +11,12 @@ export const i18n = createI18n({
messages: { messages: {
en, en,
fa, fa,
zhcn
}, },
}) })
export const languages = [ export const languages = [
{ title: 'English', value: 'en' }, { title: 'English', value: 'en' },
{ title: 'فارسی', value: 'fa' }, { title: 'فارسی', value: 'fa' },
{ title: '简体中文', value: 'zhcn' },
] ]
+172
View File
@@ -0,0 +1,172 @@
export default {
message: "欢迎",
success: "成功",
failed: "失败",
enable: "启用",
disable: "禁用",
loading: "加载中...",
confirm: "是否确定?",
yes: "确认",
no: "取消",
unlimited: "无限",
remained: "剩余",
type: "类型",
submit: "提交",
reset: "重置",
now: "当前",
network: "网络",
copyToClipboard: "复制到剪贴板",
noData: "无数据!",
invalidLogin: "登录无效!",
online: "在线",
pages: {
login: "登录",
home: "主页",
inbounds: "入站管理",
outbounds: "出站管理",
clients: "用户管理",
rules: "路由列表",
basics: "基础信息",
admins: "管理员",
settings: "设置",
},
main: {
tiles: "信息卡",
gauges: "仪表板",
charts: "图表",
infos: "信息",
gauge: {
cpu: "CPU 仪表",
mem: "RAM 仪表",
},
chart: {
cpu: "CPU 监视器",
mem: "RAM 监视器",
net: "网络带宽",
pnet: "网络数据包",
},
info: {
sys: "系统信息",
sbd: "运行信息",
host: "主机",
cpu: "CPU",
core: "核心",
uptime: "运行时间",
threads: "线程",
memory: "内存",
running: "运行状态"
}
},
objects: {
inbound: "入站",
client: "客户端",
outbound: "出站",
rule: "规则",
user: "用户",
},
actions: {
action: "操作",
add: "添加",
edit: "编辑",
del: "删除",
save: "保存",
update: "更新",
submit: "提交",
close: "关闭",
restartApp: "重启面板",
},
login: {
title: "登录",
username: "用户名",
unRules: "用户名不能为空",
password: "密码",
pwRules: "密码不能为空",
},
menu: {
logout: "退出登录",
},
admin: {
changeCred: "更改凭据",
oldPass: "当前密码",
newUname: "新用户名",
newPass: "新密码",
},
setting: {
interface: "界面",
sub: "订阅",
addr: "地址",
port: "端口",
webPath: "基本 URI",
domain: "域名",
sslKey: "SSL 密钥 (Key) 路径",
sslCert: "SSL 证书 (cert) 路径",
webUri: "面板 URI",
sessionAge: "会话最大连接数",
timeLoc: "时区",
subEncode: "启用编码",
subInfo: "启用用户信息",
path: "默认路径",
update: "自动更新时间",
subUri: "订阅 URL",
},
client: {
name: "名称",
inboundTags: "入站标签",
basics: "基础",
config: "配置",
links: "链接",
external: "外部链接",
sub: "外部订阅",
},
in: {
tag: "标签",
addr: "地址",
port: "端口",
sniffing: "嗅探",
tls: "TLS",
clients: "启用客户端",
multiplex: "多路复用",
transport: "传输",
},
transport: {
enable: "启用传输",
host: "主机",
hosts: "主机列表",
path: "路径",
},
tls : {
enable: "启用 TLS",
usePath: "使用外部路径",
useText: "使用文件内容",
certPath: "证书文件路径",
keyPath: "私钥文件路径",
cert: "证书文件内容",
key: "私钥文件内容",
},
stats: {
upload: "上传",
download: "下载",
volume: "流量",
usage: "已用",
enable: "启用统计",
graphTitle: "流量图表",
B: "B",
KB: "KB",
MB: "MB",
GB: "GB",
TB: "TB",
PB: "PB",
p: "p",
Kp: "Kp",
Mp: "Mp",
Gb: "Gb",
},
date: {
expiry: "到期",
expired: "已到期",
d: "天",
h: "时",
m: "分",
s: "秒",
}
}
+2
View File
@@ -3,6 +3,8 @@ import axios from 'axios'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
axios.defaults.baseURL = "./"
axios.interceptors.request.use( axios.interceptors.request.use(
(config) => { (config) => {
if (config.data instanceof FormData) { if (config.data instanceof FormData) {
+13
View File
@@ -1,5 +1,6 @@
import api from './api' import api from './api'
import { i18n } from '@/locales' import { i18n } from '@/locales'
import router from '@/router'
import Message from "@/store/modules/message" import Message from "@/store/modules/message"
export interface Msg { export interface Msg {
@@ -14,11 +15,23 @@ function _handleMsg(msg: any): void {
return return
} }
if(msg.msg){ if(msg.msg){
if (!msg.success && msg.msg == "Invalid login") {
sb.showMessage(i18n.global.t('invalidLogin'),'error', 5000)
logout()
return
}
const message = msg.success ? i18n.global.t('success') + ": " + i18n.global.t('actions.' + msg.msg) : i18n.global.t('failed') + ": " + msg.msg const message = msg.success ? i18n.global.t('success') + ": " + i18n.global.t('actions.' + msg.msg) : i18n.global.t('failed') + ": " + msg.msg
sb.showMessage(message, msg.success ? 'success' : 'error', 5000) sb.showMessage(message, msg.success ? 'success' : 'error', 5000)
} }
} }
export const logout = async () => {
const response = await HttpUtils.get('api/logout')
if(response.success){
router.push('/login')
}
}
function _respToMsg(resp: any): Msg { function _respToMsg(resp: any): Msg {
const data = resp.data const data = resp.data
if (data == null) { if (data == null) {
+1
View File
@@ -150,6 +150,7 @@ export namespace LinkUtil {
case TrspTypes.WebSocket: case TrspTypes.WebSocket:
const tw = <WebSocket>t const tw = <WebSocket>t
params.path = tw.path?? null params.path = tw.path?? null
params.host = tw.headers?.Host?? null
break break
case TrspTypes.gRPC: case TrspTypes.gRPC:
const tg = <gRPC>t const tg = <gRPC>t
+7 -2
View File
@@ -44,6 +44,11 @@ const routes = [
name: 'pages.basics', name: 'pages.basics',
component: () => import('@/views/Basics.vue'), component: () => import('@/views/Basics.vue'),
}, },
{
path: '/admins',
name: 'pages.admins',
component: () => import('@/views/Admins.vue'),
},
{ {
path: '/settings', path: '/settings',
name: 'pages.settings', name: 'pages.settings',
@@ -54,7 +59,7 @@ const routes = [
] ]
const router = createRouter({ const router = createRouter({
history: createWebHistory(process.env.BASE_URL), history: createWebHistory((window as any).BASE_URL),
routes, routes,
}) })
@@ -64,7 +69,7 @@ let intervalId:any
// Navigation guard to check authentication state // Navigation guard to check authentication state
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
// Check the session cookie // Check the session cookie
const sessionCookie = document.cookie.split(';').find(cookie => cookie.trim().startsWith('session=')) const sessionCookie = document.cookie.split(';').find(cookie => cookie.trim().startsWith('s-ui='))
const isAuthenticated = !!sessionCookie const isAuthenticated = !!sessionCookie
// If the route requires authentication and the user is not authenticated, redirect to /login // If the route requires authentication and the user is not authenticated, redirect to /login
+4 -4
View File
@@ -15,7 +15,7 @@ const Data = defineStore('Data', {
}), }),
actions: { actions: {
async loadData() { async loadData() {
const msg = await HttpUtils.get('/api/load', this.lastLoad >0 ? {lu: this.lastLoad} : {} ) const msg = await HttpUtils.get('api/load', this.lastLoad >0 ? {lu: this.lastLoad} : {} )
if(msg.success) { if(msg.success) {
this.lastLoad = Math.floor((new Date()).getTime()/1000) this.lastLoad = Math.floor((new Date()).getTime()/1000)
@@ -36,7 +36,7 @@ const Data = defineStore('Data', {
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)), config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)), clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
} }
const msg = await HttpUtils.post('/api/save',diff) const msg = await HttpUtils.post('api/save',diff)
if(msg.success) { if(msg.success) {
this.loadData() this.loadData()
} }
@@ -46,7 +46,7 @@ const Data = defineStore('Data', {
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]), config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)), clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
} }
const msg = await HttpUtils.post('/api/save',diff) const msg = await HttpUtils.post('api/save',diff)
if(msg.success) { if(msg.success) {
this.loadData() this.loadData()
} }
@@ -56,7 +56,7 @@ const Data = defineStore('Data', {
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)), config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
clients:JSON.stringify([{key: "clients", action: "del", index: id, obj: null}]), clients:JSON.stringify([{key: "clients", action: "del", index: id, obj: null}]),
} }
const msg = await HttpUtils.post('/api/save',diff) const msg = await HttpUtils.post('api/save',diff)
if(msg.success) { if(msg.success) {
this.loadData() this.loadData()
} }
+1 -1
View File
@@ -12,7 +12,7 @@ export interface iTls {
} }
export const defaultInTls: iTls = { export const defaultInTls: iTls = {
alpn: ['HTTP/3', 'HTTP/2', 'HTTP/1.1'], alpn: ['h3', 'h2', 'http/1.1'],
min_version: "1.2", min_version: "1.2",
max_version: "1.3", max_version: "1.3",
cipher_suites: [""], cipher_suites: [""],
+3 -1
View File
@@ -25,7 +25,9 @@ export interface HTTP extends TransportBasics {
export interface WebSocket extends TransportBasics { export interface WebSocket extends TransportBasics {
path: string path: string
headers?: {} headers?: {
Host: string
}
max_early_data?: number max_early_data?: number
early_data_header_name?: string early_data_header_name?: string
} }
+91
View File
@@ -0,0 +1,91 @@
<template>
<AdminModal
v-model="editModal.visible"
:visible="editModal.visible"
:user="editModal.user"
@close="closeEditModal"
@save="saveEditModal"
/>
<v-row>
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>users" :key="item.id">
<v-card rounded="xl" elevation="5" min-width="200" :title="item.username">
<v-card-subtitle>
Last Login
</v-card-subtitle>
<v-card-text>
<v-row>
<v-col>Date</v-col>
<v-col dir="ltr">
{{ item.lastLogin.split(" ")[0]?? '-' }}
</v-col>
</v-row>
<v-row>
<v-col>Time</v-col>
<v-col dir="ltr">
{{ item.lastLogin.split(" ")[1]?? '-' }}
</v-col>
</v-row>
<v-row>
<v-col>IP</v-col>
<v-col dir="ltr">
{{ item.lastLogin.split(" ")[2]?? '-' }}
</v-col>
</v-row>
</v-card-text>
<v-divider></v-divider>
<v-card-actions style="padding: 0;">
<v-btn icon="mdi-account-edit" @click="showEditModal(item)">
<v-icon />
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</template>
<script lang="ts" setup>
import AdminModal from '@/layouts/modals/Admin.vue';
import HttpUtils from '@/plugins/httputil';
import { Ref, ref, inject, onMounted } from 'vue';
const loading:Ref = inject('loading')?? ref(false)
const users = ref([])
onMounted(async () => {loadData()})
const loadData = async () => {
loading.value = true
const msg = await HttpUtils.get('api/users')
loading.value = false
if (msg.success) {
users.value = msg.obj
}
}
const editModal = ref({
visible: false,
user: {},
})
const showEditModal = (user: any) => {
editModal.value.user = user
editModal.value.visible = true
}
const closeEditModal = () => {
editModal.value.visible = false
}
const saveEditModal = async (data:any) => {
loading.value=true
const response = await HttpUtils.post('api/changePass',data)
if(response.success){
setTimeout(() => {
loading.value=false
editModal.value.visible = false
}, 500)
} else {
loading.value=false
}
}
</script>
-7
View File
@@ -337,10 +337,3 @@ const enableClashApi = computed({
} }
}) })
</script> </script>
<style>
.v-card-subtitle {
text-align: center;
border-bottom: 1px solid gray;
}
</style>
+3 -3
View File
@@ -24,7 +24,7 @@
@close="closeStats" @close="closeStats"
/> />
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12" justify="center" align="center">
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn> <v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
</v-col> </v-col>
</v-row> </v-row>
@@ -73,7 +73,7 @@
{{ $t('remained') }}: {{ HumanReadable.sizeFormat(item.volume - (item.up + item.down)) }} {{ $t('remained') }}: {{ HumanReadable.sizeFormat(item.volume - (item.up + item.down)) }}
</template> </template>
</v-tooltip> </v-tooltip>
{{ item.volume>0 ? HumanReadable.sizeFormat(item.up + item.down) : $t('unlimited') }} {{ HumanReadable.sizeFormat(item.up + item.down) }}
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
@@ -221,7 +221,7 @@ const buildInboundsUsers = (inboundTags:string[]) => {
// Remove flow in non tls VLESS // Remove flow in non tls VLESS
if (newInbound.type == InTypes.VLESS) { if (newInbound.type == InTypes.VLESS) {
const vlessInbound = <VLESS>newInbound const vlessInbound = <VLESS>newInbound
if (!vlessInbound.tls?.enabled) delete(clientConfig["vless"].flow) if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(clientConfig["vless"].flow)
} }
users.push(clientConfig[newInbound.type]) users.push(clientConfig[newInbound.type])
}) })
+3 -3
View File
@@ -16,7 +16,7 @@
@close="closeStats" @close="closeStats"
/> />
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12" justify="center" align="center">
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn> <v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
</v-col> </v-col>
</v-row> </v-row>
@@ -53,7 +53,7 @@
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="Object.hasOwn(item,'users')"> <v-tooltip activator="parent" dir="ltr" location="bottom" v-if="Object.hasOwn(item,'users')">
<span v-for="u in findInbounsUsers(item)">{{ u }}<br /></span> <span v-for="u in findInbounsUsers(item)">{{ u }}<br /></span>
</v-tooltip> </v-tooltip>
{{ Object.hasOwn(item,'users') ? item.users.length : '-' }} {{ Array.isArray(item.users) ? item.users.length : '-' }}
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
@@ -236,7 +236,7 @@ const buildInboundsUsers = (inbound:InboundWithUser):Inbound => {
// Remove flow in non tls VLESS // Remove flow in non tls VLESS
if (inbound.type == InTypes.VLESS) { if (inbound.type == InTypes.VLESS) {
const vlessInbound = <VLESS>inbound const vlessInbound = <VLESS>inbound
if (!vlessInbound.tls?.enabled) clientConfig["vless"].flow = undefined if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(clientConfig["vless"].flow)
} }
users.push(clientConfig[inbound.type]) users.push(clientConfig[inbound.type])
}) })
+3 -3
View File
@@ -3,12 +3,12 @@
<v-row justify="center" align="center"> <v-row justify="center" align="center">
<v-col cols="12" sm="8" md="4"> <v-col cols="12" sm="8" md="4">
<v-card> <v-card>
<v-card-title class="headline">Login</v-card-title> <v-card-title class="headline" v-text="$t('login.title')"></v-card-title>
<v-card-text> <v-card-text>
<v-form @submit.prevent="login" ref="form"> <v-form @submit.prevent="login" ref="form">
<v-text-field v-model="username" :label="$t('login.username')" :rules="usernameRules" required></v-text-field> <v-text-field v-model="username" :label="$t('login.username')" :rules="usernameRules" required></v-text-field>
<v-text-field v-model="password" :label="$t('login.password')" :rules="passwordRules" type="password" required></v-text-field> <v-text-field v-model="password" :label="$t('login.password')" :rules="passwordRules" type="password" required></v-text-field>
<v-btn :loading="loading" type="submit" color="primary" block class="mt-2">Login</v-btn> <v-btn :loading="loading" type="submit" color="primary" block class="mt-2" v-text="$t('actions.submit')"></v-btn>
</v-form> </v-form>
<v-select <v-select
density="compact" density="compact"
@@ -63,7 +63,7 @@ const router = useRouter()
const login = async () => { const login = async () => {
if (username.value == '' || password.value == '') return if (username.value == '' || password.value == '') return
loading.value=true loading.value=true
const response = await HttpUtil.post('/api/login',{user: username.value, pass: password.value}) const response = await HttpUtil.post('api/login',{user: username.value, pass: password.value})
if(response.success){ if(response.success){
setTimeout(() => { setTimeout(() => {
loading.value=false loading.value=false
+18 -8
View File
@@ -32,6 +32,9 @@
<v-col cols="12" sm="6" md="4"> <v-col cols="12" sm="6" md="4">
<v-text-field v-model="webPort" :label="$t('setting.port')" hide-details></v-text-field> <v-text-field v-model="webPort" :label="$t('setting.port')" hide-details></v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="settings.webPath" :label="$t('setting.webPath')" hide-details></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4"> <v-col cols="12" sm="6" md="4">
<v-text-field v-model="settings.webDomain" :label="$t('setting.domain')" hide-details></v-text-field> <v-text-field v-model="settings.webDomain" :label="$t('setting.domain')" hide-details></v-text-field>
</v-col> </v-col>
@@ -41,6 +44,9 @@
<v-col cols="12" sm="6" md="4"> <v-col cols="12" sm="6" md="4">
<v-text-field v-model="settings.webCertFile" :label="$t('setting.sslCert')" hide-details></v-text-field> <v-text-field v-model="settings.webCertFile" :label="$t('setting.sslCert')" hide-details></v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="settings.webURI" :label="$t('setting.webUri')" hide-details></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4"> <v-col cols="12" sm="6" md="4">
<v-text-field <v-text-field
type="number" type="number"
@@ -143,6 +149,8 @@ const settings = ref({
webPort: "2095", webPort: "2095",
webCertFile: "", webCertFile: "",
webKeyFile: "", webKeyFile: "",
webPath: "/app/",
webURI: "",
sessionMaxAge: "0", sessionMaxAge: "0",
timeLocation: "Asia/Tehran", timeLocation: "Asia/Tehran",
subListen: "", subListen: "",
@@ -166,7 +174,7 @@ const changeLocale = (l: any) => {
const loadData = async () => { const loadData = async () => {
loading.value = true loading.value = true
const msg = await HttpUtils.get('/api/setting') const msg = await HttpUtils.get('api/setting')
loading.value = false loading.value = false
if (msg.success) { if (msg.success) {
settings.value = msg.obj settings.value = msg.obj
@@ -179,7 +187,7 @@ const saveChanges = async () => {
const diff = { const diff = {
settings: JSON.stringify(FindDiff.Settings(settings.value,oldSettings.value)), settings: JSON.stringify(FindDiff.Settings(settings.value,oldSettings.value)),
} }
const msg = await HttpUtils.post('/api/save', diff) const msg = await HttpUtils.post('api/save', diff)
if (msg.success) { if (msg.success) {
loadData() loadData()
} }
@@ -190,17 +198,20 @@ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
const restartApp = async () => { const restartApp = async () => {
loading.value = true loading.value = true
const msg = await HttpUtils.post('/api/restartApp',{}) const msg = await HttpUtils.post('api/restartApp',{})
if (msg.success) { if (msg.success) {
const isTLS = settings.value.webCertFile !== "" || settings.value.webKeyFile !== "" let url = settings.value.webURI
const url = buildURL(settings.value.webDomain,settings.value.webPort.toString(),isTLS) if (url !== "") {
const isTLS = settings.value.webCertFile !== "" || settings.value.webKeyFile !== ""
url = buildURL(settings.value.webDomain,settings.value.webPort.toString(),isTLS, settings.value.webPath)
}
await sleep(3000) await sleep(3000)
window.location.replace(url) window.location.replace(url)
} }
loading.value = false loading.value = false
} }
const buildURL = (host: string, port: string, isTLS: boolean ) => { const buildURL = (host: string, port: string, isTLS: boolean, path: string) => {
if (!host || host.length == 0) host = window.location.hostname if (!host || host.length == 0) host = window.location.hostname
if (!port || port.length == 0) port = window.location.port if (!port || port.length == 0) port = window.location.port
@@ -212,7 +223,7 @@ const buildURL = (host: string, port: string, isTLS: boolean ) => {
port = `:${port}` port = `:${port}`
} }
return `${protocol}//${host}${port}/settings` return `${protocol}//${host}${port}${path}settings`
} }
const subEncode = computed({ const subEncode = computed({
@@ -245,7 +256,6 @@ const subUpdates = computed({
set: (v:number) => { settings.value.subUpdates = v.toString() } set: (v:number) => { settings.value.subUpdates = v.toString() }
}) })
const stateChange = computed(() => { const stateChange = computed(() => {
return !FindDiff.deepCompare(settings.value,oldSettings.value) return !FindDiff.deepCompare(settings.value,oldSettings.value)
}) })
+17 -3
View File
@@ -5,8 +5,18 @@ import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
// Utilities // Utilities
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { randomBytes } from 'crypto';
function getUniqueFileName(template) {
if (template.includes('.js') || template.includes('.css')) {
const hash = randomBytes(8).toString('hex');
return template.replace('[name]', hash);
}
return template;
}
export default defineConfig({ export default defineConfig({
base: '',
plugins: [ plugins: [
vue({ vue({
template: { transformAssetUrls }, template: { transformAssetUrls },
@@ -25,8 +35,12 @@ export default defineConfig({
rollupOptions: { rollupOptions: {
output: { output: {
inlineDynamicImports: true, inlineDynamicImports: true,
entryFileNames: 'assets/[name].js', entryFileNames: getUniqueFileName('assets/[name].js'),
assetFileNames: 'assets/[name].[ext]', chunkFileNames: getUniqueFileName('assets/[name].js'),
assetFileNames: (assetInfo) => {
if (assetInfo.name == "index.css") return getUniqueFileName('assets/[name].css');
return 'assets/' + assetInfo.name;
},
}, },
} }
}, },
@@ -40,7 +54,7 @@ export default defineConfig({
server: { server: {
port: 3000, port: 3000,
proxy: { proxy: {
'/api': { '/app/api': {
target: 'http://localhost:2095', target: 'http://localhost:2095',
changeOrigin: true, changeOrigin: true,
}, },
+83 -32
View File
@@ -23,25 +23,17 @@ else
fi fi
echo "The OS release is: $release" echo "The OS release is: $release"
arch=$(arch) arch() {
case "$(uname -m)" in
x86_64 | x64 | amd64) echo 'amd64' ;;
i*86 | x86) echo '386' ;;
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
armv7* | armv7 | arm) echo 'armv7' ;;
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
esac
}
if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then echo "arch: $(arch)"
arch="amd64"
elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
arch="arm64"
elif [[ $arch == "s390x" ]]; then
arch="s390x"
else
arch="amd64"
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
fi
echo "arch: ${arch}"
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
echo "s-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
exit -1
fi
os_version="" os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1) os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
@@ -50,7 +42,7 @@ if [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
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
@@ -70,10 +62,67 @@ fi
install_base() { install_base() {
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then case "${release}" in
yum install wget curl tar unzip jq -y centos)
yum -y update && yum install -y -q wget curl tar tzdata
;;
fedora)
dnf -y update && dnf install -y -q wget curl tar tzdata
;;
*)
apt-get update && apt install -y -q wget curl tar tzdata
;;
esac
}
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
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Enter the ${yellow}panel port${plain} (leave blank for existing/default value):" config_port
read -p "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):" config_path
# Sub configuration
read -p "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):" config_subPort
read -p "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):" config_subPath
# Set configs
echo -e "${yellow}Initializing, please wait...${plain}"
params=""
[ -z "$config_port" ] || params="$params -port $config_port"
[ -z "$config_path" ] || params="$params -path $config_path"
[ -z "$config_subPort" ] || params="$params -subPort $config_subPort"
[ -z "$config_subPath" ] || params="$params -subPath $config_subPath"
/usr/local/s-ui/sui setting ${params}
read -p "Do you want to change admin credentials [y/n]? ": admin_confirm
if [[ "${admin_confirm}" == "y" || "${admin_confirm}" == "Y" ]]; then
# First admin credentials
read -p "Please set up your username:" config_account
read -p "Please set up your password:" config_password
# Set credentials
echo -e "${yellow}Initializing, please wait...${plain}"
/usr/local/s-ui/sui admin -username ${config_account} -password ${config_password}
else
echo -e "${yellow}Your current admin credentials: ${plain}"
/usr/local/s-ui/sui admin -show
fi
else else
apt install wget curl tar unzip jq -y echo -e "${red}cancel...${plain}"
if [[ ! -f "/usr/local/s-ui/db/s-ui.db" ]]; then
local usernameTemp=$(head -c 6 /dev/urandom | base64)
local passwordTemp=$(head -c 6 /dev/urandom | base64)
echo -e "this is a fresh installation,will generate random login info for security concerns:"
echo -e "###############################################"
echo -e "${green}username:${usernameTemp}${plain}"
echo -e "${green}password:${passwordTemp}${plain}"
echo -e "###############################################"
echo -e "${red}if you forgot your login info,you can type ${green}s-ui${red} for configuration menu${plain}"
/usr/local/s-ui/sui admin -username ${usernameTemp} -password ${passwordTemp}
else
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type ${green}s-ui${red} for configuration menu${plain}"
fi
fi fi
} }
@@ -87,16 +136,16 @@ install_s-ui() {
exit 1 exit 1
fi fi
echo -e "Got s-ui latest version: ${last_version}, beginning the installation..." echo -e "Got s-ui latest version: ${last_version}, beginning the installation..."
wget -N --no-check-certificate -O /tmp/s-ui-linux-${arch}.tar.gz https://github.com/alireza0/s-ui/releases/download/${last_version}/s-ui-linux-${arch}.tar.gz wget -N --no-check-certificate -O /tmp/s-ui-linux-$(arch).tar.gz https://github.com/alireza0/s-ui/releases/download/${last_version}/s-ui-linux-$(arch).tar.gz
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}Dowanloading s-ui failed, please be sure that your server can access Github ${plain}" echo -e "${red}Dowanloading s-ui failed, please be sure that your server can access Github ${plain}"
exit 1 exit 1
fi fi
else else
last_version=$1 last_version=$1
url="https://github.com/alireza0/s-ui/releases/download/${last_version}/s-ui-linux-${arch}.tar.gz" url="https://github.com/alireza0/s-ui/releases/download/${last_version}/s-ui-linux-$(arch).tar.gz"
echo -e "Begining to install s-ui v$1" echo -e "Begining to install s-ui v$1"
wget -N --no-check-certificate -O /tmp/s-ui-linux-${arch}.tar.gz ${url} wget -N --no-check-certificate -O /tmp/s-ui-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}dowanload s-ui v$1 failed,please check the verison exists${plain}" echo -e "${red}dowanload s-ui v$1 failed,please check the verison exists${plain}"
exit 1 exit 1
@@ -108,23 +157,25 @@ install_s-ui() {
systemctl stop sing-box systemctl stop sing-box
fi fi
tar zxvf s-ui-linux-${arch}.tar.gz tar zxvf s-ui-linux-$(arch).tar.gz
rm s-ui-linux-${arch}.tar.gz -f rm s-ui-linux-$(arch).tar.gz -f
chmod +x s-ui/sui s-ui/bin/sing-box s-ui/bin/runSingbox.sh
wget --no-check-certificate -O /usr/bin/s-ui https://raw.githubusercontent.com/alireza0/s-ui/main/s-ui.sh
chmod +x s-ui/sui s-ui/bin/sing-box s-ui/bin/runSingbox.sh /usr/bin/s-ui
cp -rf s-ui /usr/local/ cp -rf s-ui /usr/local/
cp -f s-ui/*.service /etc/systemd/system/ cp -f s-ui/*.service /etc/systemd/system/
rm -rf s-ui rm -rf s-ui
config_after_install
systemctl daemon-reload systemctl daemon-reload
systemctl enable s-ui --now systemctl enable s-ui --now
systemctl enable sing-box --now systemctl enable sing-box --now
echo -e "${green}s-ui v${last_version}${plain} installation finished, it is up and running now..." echo -e "${green}s-ui v${last_version}${plain} installation finished, it is up and running now..."
echo -e "" echo -e ""
echo -e "s-ui usages: " s-ui help
echo -e "----------------------------------------------"
echo -e "s-ui - Enter Run S-UI"
echo -e "----------------------------------------------"
} }
echo -e "${green}Excuting...${plain}" echo -e "${green}Excuting...${plain}"
+903
View File
@@ -0,0 +1,903 @@
#!/bin/bash
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[0;33m'
plain='\033[0m'
#Add some basic function here
function LOGD() {
echo -e "${yellow}[DEG] $* ${plain}"
}
function LOGE() {
echo -e "${red}[ERR] $* ${plain}"
}
function LOGI() {
echo -e "${green}[INF] $* ${plain}"
}
# check root
[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
# Check OS and set release variable
if [[ -f /etc/os-release ]]; then
source /etc/os-release
release=$ID
elif [[ -f /usr/lib/os-release ]]; then
source /usr/lib/os-release
release=$ID
else
echo "Failed to check the system OS, please contact the author!" >&2
exit 1
fi
echo "The OS release is: $release"
os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
if [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
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
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
fi
elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 10 ]]; then
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
fi
fi
confirm() {
if [[ $# > 1 ]]; then
echo && read -p "$1 [Default$2]: " temp
if [[ x"${temp}" == x"" ]]; then
temp=$2
fi
else
read -p "$1 [y/n]: " temp
fi
if [[ x"${temp}" == x"y" || x"${temp}" == x"Y" ]]; then
return 0
else
return 1
fi
}
confirm_restart() {
confirm "Restart the ${1} service" "y"
if [[ $? == 0 ]]; then
restart
else
show_menu
fi
}
before_show_menu() {
echo && echo -n -e "${yellow}Press enter to return to the main menu: ${plain}" && read temp
show_menu
}
install() {
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/main/install.sh)
if [[ $? == 0 ]]; then
if [[ $# == 0 ]]; then
start
else
start 0
fi
fi
}
update() {
confirm "This function will forcefully reinstall the latest version, and the data will not be lost. Do you want to continue?" "n"
if [[ $? != 0 ]]; then
LOGE "Cancelled"
if [[ $# == 0 ]]; then
before_show_menu
fi
return 0
fi
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/main/install.sh)
if [[ $? == 0 ]]; then
LOGI "Update is complete, Panel has automatically restarted "
exit 0
fi
}
custom_version() {
echo "Enter the panel version (like 0.0.1):"
read panel_version
if [ -z "$panel_version" ]; then
echo "Panel version cannot be empty. Exiting."
exit 1
fi
download_link="https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh"
# Use the entered panel version in the download link
install_command="bash <(curl -Ls $download_link) $panel_version"
echo "Downloading and installing panel version $panel_version..."
eval $install_command
}
uninstall() {
confirm "Are you sure you want to uninstall the panel? sing-box will also uninstalled!" "n"
if [[ $? != 0 ]]; then
if [[ $# == 0 ]]; then
show_menu
fi
return 0
fi
systemctl stop s-ui
systemctl disable s-ui
systemctl stop sing-box
systemctl disable sing-box
rm /etc/systemd/system/s-ui.service -f
systemctl daemon-reload
systemctl reset-failed
rm /etc/s-ui/ -rf
rm /usr/local/s-ui/ -rf
echo ""
echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/local/s-ui -f${plain} to delete it."
echo ""
if [[ $# == 0 ]]; then
before_show_menu
fi
}
reset_admin() {
echo "It is not recommended to set admin's credentials to default!"
confirm "Are you sure you want to reset admin's credentials to default ?" "n"
if [[ $? == 0 ]]; then
/usr/local/s-ui/sui admin -reset
fi
before_show_menu
}
set_admin() {
echo "It is not recommended to set admin's credentials to a complex text."
read -p "Please set up your username:" config_account
read -p "Please set up your password:" config_password
/usr/local/s-ui/sui admin -username ${config_account} -password ${config_password}
before_show_menu
}
view_admin() {
/usr/local/s-ui/sui admin -show
before_show_menu
}
reset_setting() {
confirm "Are you sure you want to reset settings to default ?" "n"
if [[ $? == 0 ]]; then
/usr/local/s-ui/sui setting -reset
fi
before_show_menu
}
set_setting() {
echo -e "Enter the ${yellow}panel port${plain} (leave blank for existing/default value):"
read config_port
echo -e "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):"
read config_path
# Sub configuration
echo -e "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):"
read config_subPort
echo -e "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):"
read config_subPath
# Set configs
echo -e "${yellow}Initializing, please wait...${plain}"
params=""
[ -z "$config_port" ] || params="$params -port $config_port"
[ -z "$config_path" ] || params="$params -path $config_path"
[ -z "$config_subPort" ] || params="$params -subPort $config_subPort"
[ -z "$config_subPath" ] || params="$params -subPath $config_subPath"
/usr/local/s-ui/sui setting ${params}
before_show_menu
}
view_setting() {
/usr/local/s-ui/sui setting -show
before_show_menu
}
start() {
check_status $1
if [[ $? == 0 ]]; then
echo ""
LOGI -e "${1} is running, No need to start again, If you need to restart, please select restart"
else
systemctl start $1
sleep 2
check_status $1
if [[ $? == 0 ]]; then
LOGI "${1} Started Successfully"
else
LOGE "Failed to start ${1}, Probably because it takes longer than two seconds to start, Please check the log information later"
fi
fi
if [[ $# == 1 ]]; then
before_show_menu
fi
}
stop() {
check_status $1
if [[ $? == 1 ]]; then
echo ""
LOGI "${1} stopped, No need to stop again!"
else
systemctl stop $1
sleep 2
check_status
if [[ $? == 1 ]]; then
LOGI "${1} stopped successfully"
else
LOGE "Falied to stop ${1}, Probably because the stop time exceeds two seconds, Please check the log information later"
fi
fi
if [[ $# == 1 ]]; then
before_show_menu
fi
}
restart() {
systemctl restart $1
sleep 2
check_status $1
if [[ $? == 0 ]]; then
LOGI "${1} Restarted successfully"
else
LOGE "Failed to restart ${1}, Probably because it takes longer than two seconds to start, Please check the log information later"
fi
if [[ $# == 1 ]]; then
before_show_menu
fi
}
status() {
systemctl status s-ui -l
systemctl status sing-box -l
if [[ $# == 0 ]]; then
before_show_menu
fi
}
enable() {
systemctl enable $1
if [[ $? == 0 ]]; then
LOGI "Set ${1} to boot automatically on startup successfully"
else
LOGE "Failed to set ${1} Autostart"
fi
if [[ $# == 1 ]]; then
before_show_menu
fi
}
disable() {
systemctl disable $1
if [[ $? == 0 ]]; then
LOGI "Autostart ${1} Cancelled successfully"
else
LOGE "Failed to cancel ${1} autostart"
fi
if [[ $# == 1 ]]; then
before_show_menu
fi
}
show_log() {
journalctl -u $1.service -e --no-pager -f
if [[ $# == 1 ]]; then
before_show_menu
fi
}
update_shell() {
wget -O /usr/bin/s-ui -N --no-check-certificate https://github.com/alireza0/s-ui/raw/main/s-ui.sh
if [[ $? != 0 ]]; then
echo ""
LOGE "Failed to download script, Please check whether the machine can connect Github"
before_show_menu
else
chmod +x /usr/bin/s-ui
LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
fi
}
# 0: running, 1: not running, 2: not installed
check_status() {
if [[ ! -f "/etc/systemd/system/$1.service" ]]; then
return 2
fi
temp=$(systemctl status "$1" | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1)
if [[ x"${temp}" == x"running" ]]; then
return 0
else
return 1
fi
}
check_enabled() {
temp=$(systemctl is-enabled $1)
if [[ x"${temp}" == x"enabled" ]]; then
return 0
else
return 1
fi
}
check_uninstall() {
check_status s-ui
if [[ $? != 2 ]]; then
echo ""
LOGE "Panel is already installed, Please do not reinstall"
if [[ $# == 0 ]]; then
before_show_menu
fi
return 1
else
return 0
fi
}
check_install() {
check_status s-ui
if [[ $? == 2 ]]; then
echo ""
LOGE "Please install the panel first"
if [[ $# == 0 ]]; then
before_show_menu
fi
return 1
else
return 0
fi
}
show_status() {
check_status $1
case $? in
0)
echo -e "${1} state: ${green}Running${plain}"
show_enable_status $1
;;
1)
echo -e "${1} state: ${yellow}Not Running${plain}"
show_enable_status $1
;;
2)
echo -e "${1} state: ${red}Not Installed${plain}"
;;
esac
}
show_enable_status() {
check_enabled $1
if [[ $? == 0 ]]; then
echo -e "Start automatically: ${green}Yes${plain}"
else
echo -e "Start automatically: ${red}No${plain}"
fi
}
check_s-ui_status() {
count=$(ps -ef | grep "sui" | grep -v "grep" | wc -l)
if [[ count -ne 0 ]]; then
return 0
else
return 1
fi
}
show_s-ui_status() {
check_s-ui_status
if [[ $? == 0 ]]; then
echo -e "s-ui state: ${green}Running${plain}"
else
echo -e "s-ui state: ${red}Not Running${plain}"
fi
}
bbr_menu() {
echo -e "${green}\t1.${plain} Enable BBR"
echo -e "${green}\t2.${plain} Disable BBR"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
0)
show_menu
;;
1)
enable_bbr
;;
2)
disable_bbr
;;
*) echo "Invalid choice" ;;
esac
}
disable_bbr() {
if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
echo -e "${yellow}BBR is not currently enabled.${plain}"
exit 0
fi
# Replace BBR with CUBIC configurations
sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf
sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
# Apply changes
sysctl -p
# Verify that BBR is replaced with CUBIC
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then
echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}"
else
echo -e "${red}Failed to replace BBR with CUBIC. Please check your system configuration.${plain}"
fi
}
enable_bbr() {
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
echo -e "${green}BBR is already enabled!${plain}"
exit 0
fi
# Check the OS and install necessary packages
case "${release}" in
ubuntu | debian)
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
;;
centos | almalinux | rocky)
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" | tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
# Apply changes
sysctl -p
# Verify that BBR is enabled
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
echo -e "${green}BBR has been enabled successfully.${plain}"
else
echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
fi
}
install_acme() {
cd ~
LOGI "install acme..."
curl https://get.acme.sh | sh
if [ $? -ne 0 ]; then
LOGE "install acme failed"
return 1
else
LOGI "install acme succeed"
fi
return 0
}
ssl_cert_issue_main() {
echo -e "${green}\t1.${plain} Get SSL"
echo -e "${green}\t2.${plain} Revoke"
echo -e "${green}\t3.${plain} Force Renew"
read -p "Choose an option: " choice
case "$choice" in
1) ssl_cert_issue ;;
2)
local domain=""
read -p "Please enter your domain name to revoke the certificate: " domain
~/.acme.sh/acme.sh --revoke -d ${domain}
LOGI "Certificate revoked"
;;
3)
local domain=""
read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
~/.acme.sh/acme.sh --renew -d ${domain} --force ;;
*) echo "Invalid choice" ;;
esac
}
ssl_cert_issue() {
# 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
if [ $? -ne 0 ]; then
LOGE "install acme failed, please check logs"
exit 1
fi
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"
exit 1
else
LOGI "install socat succeed..."
fi
# 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
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
if [ ${currentCert} == ${domain} ]; then
local certInfo=$(~/.acme.sh/acme.sh --list)
LOGE "system already has certs here,can not issue again,current certs details:"
LOGI "$certInfo"
exit 1
else
LOGI "your domain is ready for issuing cert now..."
fi
# create a directory for install cert
certPath="/root/cert/${domain}"
if [ ! -d "$certPath" ]; then
mkdir -p "$certPath"
else
rm -rf "$certPath"
mkdir -p "$certPath"
fi
# 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
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
if [ $? -ne 0 ]; then
LOGE "issue certs failed,please check logs"
rm -rf ~/.acme.sh/${domain}
exit 1
else
LOGE "issue certs succeed,installing certs..."
fi
# install cert
~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \
--fullchain-file /root/cert/${domain}/fullchain.pem
if [ $? -ne 0 ]; then
LOGE "install certs failed,exit"
rm -rf ~/.acme.sh/${domain}
exit 1
else
LOGI "install certs succeed,enable auto renew..."
fi
~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ]; then
LOGE "auto renew failed, certs details:"
ls -lah cert/*
chmod 755 $certPath/*
exit 1
else
LOGI "auto renew succeed, certs details:"
ls -lah cert/*
chmod 755 $certPath/*
fi
}
ssl_cert_issue_CF() {
echo -E ""
LOGD "******Instructions for use******"
LOGI "This Acme script requires the following data:"
LOGI "1.Cloudflare Registered e-mail"
LOGI "2.Cloudflare Global API Key"
LOGI "3.The domain name that has been resolved dns to the current server by Cloudflare"
LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
confirm "Confirmed?[y/n]" "y"
if [ $? -eq 0 ]; then
# 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
if [ $? -ne 0 ]; then
LOGE "install acme failed, please check logs"
exit 1
fi
fi
CF_Domain=""
CF_GlobalKey=""
CF_AccountEmail=""
certPath=/root/cert
if [ ! -d "$certPath" ]; then
mkdir $certPath
else
rm -rf $certPath
mkdir $certPath
fi
LOGD "Please set a domain name:"
read -p "Input your domain here:" CF_Domain
LOGD "Your domain name is set to:${CF_Domain}"
LOGD "Please set the API key:"
read -p "Input your key here:" CF_GlobalKey
LOGD "Your API key is:${CF_GlobalKey}"
LOGD "Please set up registered email:"
read -p "Input your email here:" CF_AccountEmail
LOGD "Your registered email address is:${CF_AccountEmail}"
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
if [ $? -ne 0 ]; then
LOGE "Default CA, Lets'Encrypt fail, script exiting..."
exit 1
fi
export CF_Key="${CF_GlobalKey}"
export CF_Email=${CF_AccountEmail}
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log
if [ $? -ne 0 ]; then
LOGE "Certificate issuance failed, script exiting..."
exit 1
else
LOGI "Certificate issued Successfully, Installing..."
fi
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
--fullchain-file /root/cert/fullchain.cer
if [ $? -ne 0 ]; then
LOGE "Certificate installation failed, script exiting..."
exit 1
else
LOGI "Certificate installed Successfully,Turning on automatic updates..."
fi
~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ]; then
LOGE "Auto update setup Failed, script exiting..."
ls -lah cert
chmod 755 $certPath
exit 1
else
LOGI "The certificate is installed and auto-renewal is turned on, Specific information is as follows"
ls -lah cert
chmod 755 $certPath
fi
else
show_menu
fi
}
show_usage() {
echo -e "S-UI Control Menu Usage"
echo -e "------------------------------------------"
echo -e "SUBCOMMANDS:"
echo -e "s-ui - Admin Management Script"
echo -e "s-ui start - Start s-ui and sing-box"
echo -e "s-ui stop - Stop s-ui and sing-box"
echo -e "s-ui restart - Restart s-ui and sing-box"
echo -e "s-ui status - Current Status of s-ui and sing-box"
echo -e "s-ui enable - Enable Autostart on OS Startup"
echo -e "s-ui disable - Disable Autostart on OS Startup"
echo -e "s-ui log - Check s-ui Logs"
echo -e "s-ui update - Update"
echo -e "s-ui install - Install"
echo -e "s-ui uninstall - Uninstall"
echo -e "s-ui help - Control Menu Usage"
echo -e "------------------------------------------"
}
show_menu() {
echo -e "
${green}S-UI Admin Management Script ${plain}
————————————————————————————————
${green}0.${plain} Exit
————————————————————————————————
${green}1.${plain} Install
${green}2.${plain} Update
${green}3.${plain} Custom Version
${green}4.${plain} Uninstall
————————————————————————————————
${green}5.${plain} Reset admin credentials to default
${green}6.${plain} Set admin credentials
${green}7.${plain} View admin credentials
————————————————————————————————
${green}8.${plain} Reset Panel Settings
${green}9.${plain} Set Panel settings
${green}10.${plain} View Panel Settings
————————————————————————————————
${green}11.${plain} S-UI Start
${green}12.${plain} S-UI Stop
${green}13.${plain} S-UI Restart
${green}14.${plain} S-UI Check State
${green}15.${plain} S-UI Check Logs
${green}16.${plain} S-UI Enable Autostart
${green}17.${plain} S-UI Disable Autostart
————————————————————————————————
${green}18.${plain} Sing-Box Start
${green}19.${plain} Sing-Box Stop
${green}20.${plain} Sing-Box Restart
${green}21.${plain} Sing-Box Check State
${green}22.${plain} Sing-Box Check Logs
${green}23.${plain} Sing-Box Enable Autostart
${green}24.${plain} Sing-Box Disable Autostart
————————————————————————————————
${green}25.${plain} Enable or Disable BBR
${green}26.${plain} SSL Certificate Management
${green}27.${plain} Cloudflare SSL Certificate
————————————————————————————————
"
show_status s-ui
show_status sing-box
echo && read -p "Please enter your selection [0-27]: " num
case "${num}" in
0)
exit 0
;;
1)
check_uninstall && install
;;
2)
check_install && update
;;
3)
check_install && custom_version
;;
4)
check_install && uninstall
;;
5)
check_install && reset_admin
;;
6)
check_install && set_admin
;;
7)
check_install && view_admin
;;
8)
check_install && reset_setting
;;
9)
check_install && set_setting
;;
10)
check_install && view_setting
;;
11)
check_install && start s-ui
;;
12)
check_install && stop s-ui
;;
13)
check_install && restart s-ui
;;
14)
check_install && status s-ui
;;
15)
check_install && show_log s-ui
;;
16)
check_install && enable s-ui
;;
17)
check_install && disable s-ui
;;
18)
check_install && start sing-box
;;
19)
check_install && stop sing-box
;;
20)
check_install && restart sing-box
;;
21)
check_install && status sing-box
;;
22)
check_install && show_log sing-box
;;
23)
check_install && enable sing-box
;;
24)
check_install && disable sing-box
;;
25)
bbr_menu
;;
26)
ssl_cert_issue_main
;;
27)
ssl_cert_issue_CF
;;
*)
LOGE "Please enter the correct number [0-27]"
;;
esac
}
if [[ $# > 0 ]]; then
case $1 in
"start")
check_install 0 && start s-ui 0 && start sing-box 0
;;
"stop")
check_install 0 && stop s-ui 0 && stop sing-box 0
;;
"restart")
check_install 0 && restart s-ui 0 && restart sing-box 0
;;
"status")
check_install 0 && status 0
;;
"enable")
check_install 0 && enable s-ui 0 && enable sing-box 0
;;
"disable")
check_install 0 && disable s-ui 0 && disable sing-box 0
;;
"log")
check_install 0 && show_log s-ui 0
;;
"update")
check_install 0 && update 0
;;
"install")
check_uninstall 0 && install 0
;;
"uninstall")
check_install 0 && uninstall 0
;;
*) show_usage ;;
esac
else
show_menu
fi