0.0.1
This commit is contained in:
@@ -51,9 +51,9 @@ func InitDB(dbPath string) error {
|
|||||||
var gormLogger logger.Interface
|
var gormLogger logger.Interface
|
||||||
|
|
||||||
if config.IsDebug() {
|
if config.IsDebug() {
|
||||||
gormLogger = logger.Discard
|
|
||||||
} else {
|
|
||||||
gormLogger = logger.Default
|
gormLogger = logger.Default
|
||||||
|
} else {
|
||||||
|
gormLogger = logger.Discard
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &gorm.Config{
|
c := &gorm.Config{
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"fmt"
|
||||||
|
"x-ui/util/json_util"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,32 +25,32 @@ type User struct {
|
|||||||
|
|
||||||
type Inbound struct {
|
type Inbound struct {
|
||||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
UserId int `json:"user_id" form:"user_id"`
|
UserId int `json:"-"`
|
||||||
Up int64 `json:"up" form:"up"`
|
Up int64 `json:"up"`
|
||||||
Down int64 `json:"down" form:"down"`
|
Down int64 `json:"down"`
|
||||||
Remark string `json:"remark" form:"remark"`
|
Remark string `json:"remark" form:"remark"`
|
||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
ExpiryTime int64 `json:"expiry_time" form:"expiry_time"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
|
|
||||||
// config part
|
// config part
|
||||||
Listen string `json:"listen" form:"listen"`
|
Listen string `json:"listen" form:"listen"`
|
||||||
Port int `json:"port" form:"port"`
|
Port int `json:"port" form:"port" gorm:"unique"`
|
||||||
Protocol Protocol `json:"protocol" form:"protocol"`
|
Protocol Protocol `json:"protocol" form:"protocol"`
|
||||||
Settings string `json:"settings" form:"settings"`
|
Settings string `json:"settings" form:"settings"`
|
||||||
StreamSettings string `json:"stream_settings" form:"stream_settings"`
|
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
||||||
Tag string `json:"tag" form:"tag"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
||||||
return &xray.InboundConfig{
|
return &xray.InboundConfig{
|
||||||
Listen: json.RawMessage(i.Listen),
|
Listen: json_util.RawMessage(fmt.Sprintf("\"%s\"", i.Listen)),
|
||||||
Port: i.Port,
|
Port: i.Port,
|
||||||
Protocol: string(i.Protocol),
|
Protocol: string(i.Protocol),
|
||||||
Settings: json.RawMessage(i.Settings),
|
Settings: json_util.RawMessage(i.Settings),
|
||||||
StreamSettings: json.RawMessage(i.StreamSettings),
|
StreamSettings: json_util.RawMessage(i.StreamSettings),
|
||||||
Tag: i.Tag,
|
Tag: i.Tag,
|
||||||
Sniffing: json.RawMessage(i.Sniffing),
|
Sniffing: json_util.RawMessage(i.Sniffing),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
148
install.sh
Normal file
148
install.sh
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
red='\033[0;31m'
|
||||||
|
green='\033[0;32m'
|
||||||
|
yellow='\033[0;33m'
|
||||||
|
plain='\033[0m'
|
||||||
|
|
||||||
|
cur_dir=$(pwd)
|
||||||
|
|
||||||
|
# check root
|
||||||
|
[[ $EUID -ne 0 ]] && echo -e "${red}错误:${plain} 必须使用root用户运行此脚本!\n" && exit 1
|
||||||
|
|
||||||
|
# check os
|
||||||
|
if [[ -f /etc/redhat-release ]]; then
|
||||||
|
release="centos"
|
||||||
|
elif cat /etc/issue | grep -Eqi "debian"; then
|
||||||
|
release="debian"
|
||||||
|
elif cat /etc/issue | grep -Eqi "ubuntu"; then
|
||||||
|
release="ubuntu"
|
||||||
|
elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then
|
||||||
|
release="centos"
|
||||||
|
elif cat /proc/version | grep -Eqi "debian"; then
|
||||||
|
release="debian"
|
||||||
|
elif cat /proc/version | grep -Eqi "ubuntu"; then
|
||||||
|
release="ubuntu"
|
||||||
|
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
|
||||||
|
release="centos"
|
||||||
|
else
|
||||||
|
echo -e "${red}未检测到系统版本,请联系脚本作者!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
arch=$(arch)
|
||||||
|
|
||||||
|
if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then
|
||||||
|
arch="amd64"
|
||||||
|
elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
|
||||||
|
arch="arm64"
|
||||||
|
else
|
||||||
|
arch="amd64"
|
||||||
|
echo -e "${red}检测架构失败,使用默认架构: ${arch}${plain}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "架构: ${arch}"
|
||||||
|
|
||||||
|
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ] ; then
|
||||||
|
echo "本软件不支持 32 位系统(x86),请使用 64 位系统(x86_64),如果检测有误,请联系作者"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
os_version=""
|
||||||
|
|
||||||
|
# os version
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release)
|
||||||
|
fi
|
||||||
|
if [[ -z "$os_version" && -f /etc/lsb-release ]]; then
|
||||||
|
os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ x"${release}" == x"centos" ]]; then
|
||||||
|
if [[ ${os_version} -le 6 ]]; then
|
||||||
|
echo -e "${red}请使用 CentOS 7 或更高版本的系统!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
elif [[ x"${release}" == x"ubuntu" ]]; then
|
||||||
|
if [[ ${os_version} -lt 16 ]]; then
|
||||||
|
echo -e "${red}请使用 Ubuntu 16 或更高版本的系统!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
elif [[ x"${release}" == x"debian" ]]; then
|
||||||
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
|
echo -e "${red}请使用 Debian 8 或更高版本的系统!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_base() {
|
||||||
|
if [[ x"${release}" == x"centos" ]]; then
|
||||||
|
yum install wget curl tar -y
|
||||||
|
else
|
||||||
|
apt install wget curl tar -y
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_x-ui() {
|
||||||
|
systemctl stop x-ui
|
||||||
|
cd /usr/local/
|
||||||
|
if [[ -e /usr/local/x-ui/ ]]; then
|
||||||
|
rm /usr/local/x-ui/ -rf
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# == 0 ] ;then
|
||||||
|
last_version=$(curl -Ls "https://api.github.com/repos/sprov065/x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||||
|
if [[ ! -n "$last_version" ]]; then
|
||||||
|
echo -e "${red}检测 x-ui 版本失败,可能是超出 Github API 限制,请稍后再试,或手动指定 x-ui 版本安装${plain}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "检测到 x-ui 最新版本:${last_version},开始安装"
|
||||||
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/sprov065/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
echo -e "${red}下载 x-ui 失败,请确保你的服务器能够下载 Github 的文件${plain}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
last_version=$1
|
||||||
|
url="https://github.com/sprov065/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
||||||
|
echo -e "开始安装 x-ui v$1"
|
||||||
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
echo -e "${red}下载 x-ui v$1 失败,请确保此版本存在${plain}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar zxvf x-ui-linux-${arch}.tar.gz
|
||||||
|
rm x-ui-linux-${arch}.tar.gz -f
|
||||||
|
cd x-ui
|
||||||
|
chmod +x x-ui bin/xray-x-ui-linux-${arch}
|
||||||
|
cp -f x-ui.service /etc/systemd/system/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable x-ui
|
||||||
|
systemctl start x-ui
|
||||||
|
echo -e "${green}x-ui v${last_version}${plain} 安装完成,面板已启动,"
|
||||||
|
echo -e ""
|
||||||
|
echo -e "如果是全新安装,默认网页端口为 ${green}54321${plain},用户名和密码默认都是 ${green}admin${plain}"
|
||||||
|
echo -e "请自行确保此端口没有被其他程序占用,${yellow}并且确保 54321 端口已放行${plain}"
|
||||||
|
# echo -e "若想将 54321 修改为其它端口,输入 x-ui 命令进行修改,同样也要确保你修改的端口也是放行的"
|
||||||
|
echo -e ""
|
||||||
|
echo -e "如果是更新面板,则按你之前的方式访问面板"
|
||||||
|
echo -e ""
|
||||||
|
curl -o /usr/bin/x-ui -Ls https://raw.githubusercontent.com/sprov065/x-ui/master/x-ui.sh
|
||||||
|
chmod +x /usr/bin/x-ui
|
||||||
|
echo -e "x-ui 管理脚本使用方法: "
|
||||||
|
echo -e "----------------------------------------------"
|
||||||
|
echo -e "x-ui - 显示管理菜单 (功能更多)"
|
||||||
|
echo -e "x-ui start - 启动 x-ui 面板"
|
||||||
|
echo -e "x-ui stop - 停止 x-ui 面板"
|
||||||
|
echo -e "x-ui restart - 重启 x-ui 面板"
|
||||||
|
echo -e "x-ui status - 查看 x-ui 状态"
|
||||||
|
echo -e "x-ui enable - 设置 x-ui 开机自启"
|
||||||
|
echo -e "x-ui disable - 取消 x-ui 开机自启"
|
||||||
|
echo -e "x-ui log - 查看 x-ui 日志"
|
||||||
|
echo -e "x-ui update - 更新 x-ui 面板"
|
||||||
|
echo -e "x-ui install - 安装 x-ui 面板"
|
||||||
|
echo -e "x-ui uninstall - 卸载 x-ui 面板"
|
||||||
|
echo -e "----------------------------------------------"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo -e "${green}开始安装${plain}"
|
||||||
|
install_base
|
||||||
|
install_x-ui $1
|
||||||
115
main.go
115
main.go
@@ -12,8 +12,10 @@ import (
|
|||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/v2ui"
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
|
"x-ui/web/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this function call global.setWebServer
|
// this function call global.setWebServer
|
||||||
@@ -46,7 +48,8 @@ func runWebServer() {
|
|||||||
setWebServer(server)
|
setWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
@@ -60,7 +63,8 @@ func runWebServer() {
|
|||||||
setWebServer(server)
|
setWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Println(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
@@ -68,8 +72,48 @@ func runWebServer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func v2ui(dbPath string) {
|
func resetSetting() {
|
||||||
// migrate from v2-ui
|
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, username string, password 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.Printf("set port %v success", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if username != "" || password != "" {
|
||||||
|
userService := service.UserService{}
|
||||||
|
err := userService.UpdateFirstUser(username, password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set username and password failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set username and password success")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -78,24 +122,77 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showVersion bool
|
||||||
|
flag.BoolVar(&showVersion, "v", false, "show version")
|
||||||
|
|
||||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
|
|
||||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
||||||
var dbPath string
|
var dbPath string
|
||||||
v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path")
|
v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path")
|
||||||
|
|
||||||
switch flag.Arg(0) {
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
|
var port int
|
||||||
|
var username string
|
||||||
|
var password string
|
||||||
|
var reset bool
|
||||||
|
settingCmd.BoolVar(&reset, "reset", false, "reset all setting")
|
||||||
|
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||||
|
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||||
|
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||||
|
|
||||||
|
oldUsage := flag.Usage
|
||||||
|
flag.Usage = func() {
|
||||||
|
oldUsage()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Commands:")
|
||||||
|
fmt.Println(" run run web panel")
|
||||||
|
fmt.Println(" v2-ui migrate form v2-ui")
|
||||||
|
fmt.Println(" setting set settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
if showVersion {
|
||||||
|
fmt.Println(config.GetVersion())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch os.Args[1] {
|
||||||
case "run":
|
case "run":
|
||||||
runCmd.Parse(os.Args[2:])
|
err := runCmd.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
runWebServer()
|
runWebServer()
|
||||||
case "v2-ui":
|
case "v2-ui":
|
||||||
v2uiCmd.Parse(os.Args[2:])
|
err := v2uiCmd.Parse(os.Args[2:])
|
||||||
v2ui(dbPath)
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = v2ui.MigrateFromV2UI(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("migrate from v2-ui failed:", err)
|
||||||
|
}
|
||||||
|
case "setting":
|
||||||
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if reset {
|
||||||
|
resetSetting()
|
||||||
|
} else {
|
||||||
|
updateSetting(port, username, password)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("excepted 'run' or 'v2-ui' subcommands")
|
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
v2uiCmd.Usage()
|
v2uiCmd.Usage()
|
||||||
|
fmt.Println()
|
||||||
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,24 @@
|
|||||||
package json_util
|
package json_util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"errors"
|
||||||
"reflect"
|
|
||||||
"x-ui/util/reflect_util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
type RawMessage []byte
|
||||||
MarshalJSON 特殊处理 json.RawMessage
|
|
||||||
|
|
||||||
当 json.RawMessage 不为 nil 且 len() 为 0 时,MarshalJSON 将会解析报错
|
// MarshalJSON 自定义 json.RawMessage 默认行为
|
||||||
*/
|
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
func MarshalJSON(i interface{}) ([]byte, error) {
|
if len(m) == 0 {
|
||||||
m := map[string]interface{}{}
|
return []byte("null"), nil
|
||||||
t := reflect.TypeOf(i).Elem()
|
|
||||||
v := reflect.ValueOf(i).Elem()
|
|
||||||
fields := reflect_util.GetFields(t)
|
|
||||||
for _, field := range fields {
|
|
||||||
key := field.Tag.Get("json")
|
|
||||||
if key == "" || key == "-" {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
fieldV := v.FieldByName(field.Name)
|
return m, nil
|
||||||
value := fieldV.Interface()
|
}
|
||||||
switch value.(type) {
|
|
||||||
case json.RawMessage:
|
// UnmarshalJSON sets *m to a copy of data.
|
||||||
value := value.(json.RawMessage)
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||||
if len(value) > 0 {
|
if m == nil {
|
||||||
m[key] = value
|
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||||
}
|
}
|
||||||
default:
|
*m = append((*m)[0:0], data...)
|
||||||
m[key] = value
|
return nil
|
||||||
}
|
|
||||||
}
|
|
||||||
return json.Marshal(m)
|
|
||||||
}
|
}
|
||||||
|
|||||||
7
v2ui/v2ui.go
Normal file
7
v2ui/v2ui.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package v2ui
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func MigrateFromV2UI(dbPath string) error {
|
||||||
|
return errors.New("not support right now")
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-space {
|
.ant-space {
|
||||||
display: block;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-layout-sider-zero-width-trigger {
|
.ant-layout-sider-zero-width-trigger {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class DBInbound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(address="") {
|
genLink(address = "") {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(address, this.remark);
|
return inbound.genLink(address, this.remark);
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ class DBInbound {
|
|||||||
|
|
||||||
class AllSetting {
|
class AllSetting {
|
||||||
webListen = "";
|
webListen = "";
|
||||||
webPort = 65432;
|
webPort = 54321;
|
||||||
webCertFile = "";
|
webCertFile = "";
|
||||||
webKeyFile = "";
|
webKeyFile = "";
|
||||||
webBasePath = "/";
|
webBasePath = "/";
|
||||||
|
|||||||
@@ -223,6 +223,9 @@ class ObjectUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static cloneProps(dest, src, ...ignoreProps) {
|
static cloneProps(dest, src, ...ignoreProps) {
|
||||||
|
if (dest == null || src == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ignoreEmpty = this.isArrEmpty(ignoreProps);
|
const ignoreEmpty = this.isArrEmpty(ignoreProps);
|
||||||
for (const key of Object.keys(src)) {
|
for (const key of Object.keys(src)) {
|
||||||
if (!src.hasOwnProperty(key)) {
|
if (!src.hasOwnProperty(key)) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/atomic"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
@@ -15,8 +14,6 @@ import (
|
|||||||
type InboundController struct {
|
type InboundController struct {
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
|
|
||||||
isNeedXrayRestart atomic.Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInboundController(g *gin.RouterGroup) *InboundController {
|
func NewInboundController(g *gin.RouterGroup) *InboundController {
|
||||||
@@ -39,12 +36,12 @@ func (a *InboundController) startTask() {
|
|||||||
webServer := global.GetWebServer()
|
webServer := global.GetWebServer()
|
||||||
c := webServer.GetCron()
|
c := webServer.GetCron()
|
||||||
c.AddFunc("@every 10s", func() {
|
c.AddFunc("@every 10s", func() {
|
||||||
if a.isNeedXrayRestart.Load() {
|
if a.xrayService.IsNeedRestart() {
|
||||||
|
a.xrayService.SetIsNeedRestart(false)
|
||||||
err := a.xrayService.RestartXray()
|
err := a.xrayService.RestartXray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("restart xray failed:", err)
|
logger.Error("restart xray failed:", err)
|
||||||
}
|
}
|
||||||
a.isNeedXrayRestart.Store(false)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -73,7 +70,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
err = a.inboundService.AddInbound(inbound)
|
err = a.inboundService.AddInbound(inbound)
|
||||||
jsonMsg(c, "添加", err)
|
jsonMsg(c, "添加", err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.isNeedXrayRestart.Store(true)
|
a.xrayService.SetIsNeedRestart(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +83,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
err = a.inboundService.DelInbound(id)
|
err = a.inboundService.DelInbound(id)
|
||||||
jsonMsg(c, "删除", err)
|
jsonMsg(c, "删除", err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.isNeedXrayRestart.Store(true)
|
a.xrayService.SetIsNeedRestart(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +104,6 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
err = a.inboundService.UpdateInbound(inbound)
|
err = a.inboundService.UpdateInbound(inbound)
|
||||||
jsonMsg(c, "修改", err)
|
jsonMsg(c, "修改", err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.isNeedXrayRestart.Store(true)
|
a.xrayService.SetIsNeedRestart(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,28 +38,19 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
status := a.serverService.GetStatus(a.lastStatus)
|
a.lastStatus = a.serverService.GetStatus(a.lastStatus)
|
||||||
a.lastStatus = status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) startTask() {
|
func (a *ServerController) startTask() {
|
||||||
webServer := global.GetWebServer()
|
webServer := global.GetWebServer()
|
||||||
ctx := webServer.GetCtx()
|
c := webServer.GetCron()
|
||||||
go func() {
|
c.AddFunc("@every 2s", func() {
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(a.lastGetStatusTime) > time.Minute*3 {
|
if now.Sub(a.lastGetStatusTime) > time.Minute*3 {
|
||||||
time.Sleep(time.Second * 2)
|
return
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
a.refreshStatus()
|
a.refreshStatus()
|
||||||
}
|
})
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) status(c *gin.Context) {
|
func (a *ServerController) status(c *gin.Context) {
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"time"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
"x-ui/web/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type updateUserForm struct {
|
||||||
|
OldUsername string `json:"oldUsername" form:"oldUsername"`
|
||||||
|
OldPassword string `json:"oldPassword" form:"oldPassword"`
|
||||||
|
NewUsername string `json:"newUsername" form:"newUsername"`
|
||||||
|
NewPassword string `json:"newPassword" form:"newPassword"`
|
||||||
|
}
|
||||||
|
|
||||||
type SettingController struct {
|
type SettingController struct {
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
|
userService service.UserService
|
||||||
|
panelService service.PanelService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSettingController(g *gin.RouterGroup) *SettingController {
|
func NewSettingController(g *gin.RouterGroup) *SettingController {
|
||||||
@@ -21,6 +33,8 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
g.POST("/all", a.getAllSetting)
|
g.POST("/all", a.getAllSetting)
|
||||||
g.POST("/update", a.updateSetting)
|
g.POST("/update", a.updateSetting)
|
||||||
|
g.POST("/updateUser", a.updateUser)
|
||||||
|
g.POST("/restartPanel", a.restartPanel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
@@ -42,3 +56,33 @@ func (a *SettingController) updateSetting(c *gin.Context) {
|
|||||||
err = a.settingService.UpdateAllSetting(allSetting)
|
err = a.settingService.UpdateAllSetting(allSetting)
|
||||||
jsonMsg(c, "修改设置", err)
|
jsonMsg(c, "修改设置", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) updateUser(c *gin.Context) {
|
||||||
|
form := &updateUserForm{}
|
||||||
|
err := c.ShouldBind(form)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "修改用户", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := session.GetLoginUser(c)
|
||||||
|
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
||||||
|
jsonMsg(c, "修改用户", errors.New("原用户名或原密码错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if form.NewUsername == "" || form.NewPassword == "" {
|
||||||
|
jsonMsg(c, "修改用户", errors.New("新用户名和新密码不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
||||||
|
if err == nil {
|
||||||
|
user.Username = form.NewUsername
|
||||||
|
user.Password = form.NewPassword
|
||||||
|
session.SetLoginUser(c, user)
|
||||||
|
}
|
||||||
|
jsonMsg(c, "修改用户", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) restartPanel(c *gin.Context) {
|
||||||
|
err := a.panelService.RestartPanel(time.Second * 3)
|
||||||
|
jsonMsg(c, "重启面板", err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(s.WebBasePath, "/") {
|
if !strings.HasPrefix(s.WebBasePath, "/") {
|
||||||
return common.NewErrorf("web base path must start with '/' : <%v>", s.WebBasePath)
|
s.WebBasePath = "/" + s.WebBasePath
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(s.WebBasePath, "/") {
|
if !strings.HasSuffix(s.WebBasePath, "/") {
|
||||||
return common.NewErrorf("web base path must end with '/' : <%v>", s.WebBasePath)
|
s.WebBasePath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
xrayConfig := &xray.Config{}
|
xrayConfig := &xray.Config{}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>面板设置</span>
|
<span>面板设置</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}xui/clients">
|
<!--<a-menu-item key="{{ .base_path }}xui/clients">-->
|
||||||
<a-icon type="laptop"></a-icon>
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
<span>客户端</span>
|
<!-- <span>客户端</span>-->
|
||||||
</a-menu-item>
|
<!--</a-menu-item>-->
|
||||||
<a-sub-menu>
|
<a-sub-menu>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<a-icon type="link"></a-icon>
|
<a-icon type="link"></a-icon>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'textarea'">
|
<template v-else-if="type === 'textarea'">
|
||||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 6, maxRows: 6 }"></a-textarea>
|
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-tag v-if="true" color="red" style="margin-bottom: 10px">
|
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
||||||
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -32,11 +32,7 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
total traffic:
|
total traffic:
|
||||||
<a-popconfirm title="Are you sure you want to reset all traffic to 0? It\'s unrecoverable"
|
|
||||||
@confirm="resetAllTraffic()"
|
|
||||||
ok-text="confirm" cancel-text="cancel">
|
|
||||||
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
||||||
</a-popconfirm>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
number of accounts:
|
number of accounts:
|
||||||
@@ -112,26 +108,21 @@
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'traffic' },
|
scopedSlots: { customRender: 'traffic' },
|
||||||
}, {
|
// }, {
|
||||||
title: "settings",
|
// title: "settings",
|
||||||
align: 'center',
|
// align: 'center',
|
||||||
width: 60,
|
// width: 60,
|
||||||
scopedSlots: { customRender: 'settings' },
|
// scopedSlots: { customRender: 'settings' },
|
||||||
}, {
|
// }, {
|
||||||
title: "streamSettings",
|
// title: "streamSettings",
|
||||||
align: 'center',
|
// align: 'center',
|
||||||
width: 60,
|
// width: 60,
|
||||||
scopedSlots: { customRender: 'streamSettings' },
|
// scopedSlots: { customRender: 'streamSettings' },
|
||||||
}, {
|
}, {
|
||||||
title: "enable",
|
title: "enable",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'enable' },
|
scopedSlots: { customRender: 'enable' },
|
||||||
}, {
|
|
||||||
title: "expiryTime",
|
|
||||||
align: 'center',
|
|
||||||
width: 60,
|
|
||||||
scopedSlots: { customRender: 'expiryTime' },
|
|
||||||
}, {
|
}, {
|
||||||
title: "action",
|
title: "action",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -213,7 +204,7 @@
|
|||||||
port: inbound.port,
|
port: inbound.port,
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
stream_settings: inbound.stream.toString(),
|
streamSettings: inbound.stream.toString(),
|
||||||
sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
|
sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
|
||||||
};
|
};
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/xui/inbound/add', data, inModal);
|
||||||
@@ -227,7 +218,7 @@
|
|||||||
port: inbound.port,
|
port: inbound.port,
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
stream_settings: inbound.stream.toString(),
|
streamSettings: inbound.stream.toString(),
|
||||||
sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
|
sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
@@ -249,30 +240,13 @@
|
|||||||
const link = dbInbound.genLink(address);
|
const link = dbInbound.genLink(address);
|
||||||
qrModal.show('二维码', link);
|
qrModal.show('二维码', link);
|
||||||
},
|
},
|
||||||
resetTraffic(inbound) {
|
|
||||||
this.submit(`/xui/reset_traffic/${inbound.id}`);
|
|
||||||
},
|
|
||||||
resetAllTraffic() {
|
|
||||||
this.submit('/xui/reset_all_traffic');
|
|
||||||
},
|
|
||||||
switchEnable(dbInbound) {
|
switchEnable(dbInbound) {
|
||||||
const data = {
|
this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
|
||||||
remark: dbInbound.remark,
|
|
||||||
enable: dbInbound.enable,
|
|
||||||
|
|
||||||
listen: dbInbound.listen,
|
|
||||||
port: dbInbound.port,
|
|
||||||
protocol: dbInbound.protocol,
|
|
||||||
settings: dbInbound.settings,
|
|
||||||
stream_settings: dbInbound.stream,
|
|
||||||
sniffing: dbInbound.sniffing,
|
|
||||||
};
|
|
||||||
this.submit(`/xui/inbound/update/${dbInbound.id}`, data);
|
|
||||||
},
|
},
|
||||||
async submit(url, data, modal) {
|
async submit(url, data, modal) {
|
||||||
const msg = await HttpUtil.postWithModal(url, data, modal);
|
const msg = await HttpUtil.postWithModal(url, data, modal);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,7 +31,10 @@
|
|||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">保存配置</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">保存配置</a-button>
|
||||||
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">重启面板</a-button>
|
||||||
|
</a-space>
|
||||||
<a-tabs default-active-key="1">
|
<a-tabs default-active-key="1">
|
||||||
<a-tab-pane key="1" tab="面板配置">
|
<a-tab-pane key="1" tab="面板配置">
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" style="background: white">
|
||||||
@@ -45,16 +48,21 @@
|
|||||||
<a-tab-pane key="2" tab="用户设置">
|
<a-tab-pane key="2" tab="用户设置">
|
||||||
<a-form style="background: white; padding: 20px">
|
<a-form style="background: white; padding: 20px">
|
||||||
<a-form-item label="原用户名">
|
<a-form-item label="原用户名">
|
||||||
<a-input></a-input>
|
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="原密码">
|
<a-form-item label="原密码">
|
||||||
<a-input></a-input>
|
<a-input type="password" v-model="user.oldPassword"
|
||||||
|
style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="新用户名">
|
<a-form-item label="新用户名">
|
||||||
<a-input></a-input>
|
<a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="新密码">
|
<a-form-item label="新密码">
|
||||||
<a-input></a-input>
|
<a-input type="password" v-model="user.newPassword"
|
||||||
|
style="max-width: 300px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-button type="primary" @click="updateUser">修改</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@@ -87,6 +95,7 @@
|
|||||||
oldAllSetting: new AllSetting(),
|
oldAllSetting: new AllSetting(),
|
||||||
allSetting: new AllSetting(),
|
allSetting: new AllSetting(),
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
|
user: {},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -109,6 +118,33 @@
|
|||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async updateUser() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.user = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async restartPanel() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
this.$confirm({
|
||||||
|
title: '重启面板',
|
||||||
|
content: '确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息',
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: () => resolve(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/xui/setting/restartPanel");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.loading(true);
|
||||||
|
await PromiseUtil.sleep(5000);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
26
web/service/panel.go
Normal file
26
web/service/panel.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"x-ui/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PanelService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PanelService) RestartPanel(delay time.Duration) error {
|
||||||
|
p, err := os.FindProcess(syscall.Getpid())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
time.Sleep(delay)
|
||||||
|
err := p.Signal(syscall.SIGHUP)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("send signal SIGHUP failed:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -77,7 +77,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
T: now,
|
T: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
percents, err := cpu.Percent(time.Second*2, false)
|
percents, err := cpu.Percent(0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get cpu percent failed:", err)
|
logger.Warning("get cpu percent failed:", err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ var xrayTemplateConfig string
|
|||||||
var defaultValueMap = map[string]string{
|
var defaultValueMap = map[string]string{
|
||||||
"xrayTemplateConfig": xrayTemplateConfig,
|
"xrayTemplateConfig": xrayTemplateConfig,
|
||||||
"webListen": "",
|
"webListen": "",
|
||||||
"webPort": "65432",
|
"webPort": "54321",
|
||||||
"webCertFile": "",
|
"webCertFile": "",
|
||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
@@ -109,7 +109,7 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
|||||||
|
|
||||||
func (s *SettingService) ResetSettings() error {
|
func (s *SettingService) ResetSettings() error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return db.Delete(model.Setting{}).Error
|
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) {
|
||||||
@@ -152,6 +152,10 @@ func (s *SettingService) getString(key string) (string, error) {
|
|||||||
return setting.Value, nil
|
return setting.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) setString(key string, value string) error {
|
||||||
|
return s.saveSetting(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) getInt(key string) (int, error) {
|
func (s *SettingService) getInt(key string) (int, error) {
|
||||||
str, err := s.getString(key)
|
str, err := s.getString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -160,6 +164,10 @@ func (s *SettingService) getInt(key string) (int, error) {
|
|||||||
return strconv.Atoi(str)
|
return strconv.Atoi(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) setInt(key string, value int) error {
|
||||||
|
return s.setString(key, strconv.Itoa(value))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetXrayConfigTemplate() (string, error) {
|
func (s *SettingService) GetXrayConfigTemplate() (string, error) {
|
||||||
return s.getString("xrayTemplateConfig")
|
return s.getString("xrayTemplateConfig")
|
||||||
}
|
}
|
||||||
@@ -172,6 +180,10 @@ func (s *SettingService) GetPort() (int, error) {
|
|||||||
return s.getInt("webPort")
|
return s.getInt("webPort")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetPort(port int) error {
|
||||||
|
return s.setInt("webPort", port)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetCertFile() (string, error) {
|
func (s *SettingService) GetCertFile() (string, error) {
|
||||||
return s.getString("webCertFile")
|
return s.getString("webCertFile")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
@@ -26,3 +27,33 @@ func (s *UserService) CheckUser(username string, password string) *model.User {
|
|||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) UpdateUser(id int, username string, password string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Model(model.User{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("username", username).
|
||||||
|
Update("password", password).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
||||||
|
if username == "" {
|
||||||
|
return errors.New("username can not be empty")
|
||||||
|
} else if password == "" {
|
||||||
|
return errors.New("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
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"go.uber.org/atomic"
|
||||||
"sync"
|
"sync"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
@@ -14,6 +15,8 @@ var result string
|
|||||||
type XrayService struct {
|
type XrayService struct {
|
||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
settingService SettingService
|
settingService SettingService
|
||||||
|
|
||||||
|
isNeedXrayRestart atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) IsXrayRunning() bool {
|
func (s *XrayService) IsXrayRunning() bool {
|
||||||
@@ -84,15 +87,19 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, error) {
|
|||||||
func (s *XrayService) RestartXray() error {
|
func (s *XrayService) RestartXray() error {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
if p != nil {
|
|
||||||
p.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
xrayConfig, err := s.GetXrayConfig()
|
xrayConfig, err := s.GetXrayConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p != nil {
|
||||||
|
if p.GetConfig().Equals(xrayConfig) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
p = xray.NewProcess(xrayConfig)
|
p = xray.NewProcess(xrayConfig)
|
||||||
result = ""
|
result = ""
|
||||||
return p.Start()
|
return p.Start()
|
||||||
@@ -106,3 +113,11 @@ func (s *XrayService) StopXray() error {
|
|||||||
}
|
}
|
||||||
return errors.New("xray is not running")
|
return errors.New("xray is not running")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *XrayService) SetIsNeedRestart(needRestart bool) {
|
||||||
|
s.isNeedXrayRestart.Store(needRestart)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XrayService) IsNeedRestart() bool {
|
||||||
|
return s.isNeedXrayRestart.Load()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
"username" = "用户名"
|
"username" = "用户名"
|
||||||
"password" = "密码"
|
"password" = "密码"
|
||||||
"login" = "登录"
|
"login" = "登录"
|
||||||
"confirm" = "confirm"
|
"confirm" = "确定"
|
||||||
"cancel" = "cancel"
|
"cancel" = "取消"
|
||||||
"close" = "close"
|
"close" = "关闭"
|
||||||
"copy" = "copy"
|
"copy" = "复制"
|
||||||
"copied" = "copied"
|
"copied" = "已复制"
|
||||||
"download" = "download"
|
"download" = "下载"
|
||||||
"remark" = "remark"
|
"remark" = "备注"
|
||||||
"enable" = "enable"
|
"enable" = "启用"
|
||||||
"protocol" = "protocol"
|
"protocol" = "协议"
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
"username" = "用戶名"
|
"username" = "用戶名"
|
||||||
"password" = "密碼"
|
"password" = "密碼"
|
||||||
"login" = "登錄"
|
"login" = "登錄"
|
||||||
"confirm" = "confirm"
|
"confirm" = "確定"
|
||||||
"cancel" = "cancel"
|
"cancel" = "取消"
|
||||||
"close" = "close"
|
"close" = "關閉"
|
||||||
"copy" = "copy"
|
"copy" = "複製"
|
||||||
"copied" = "copied"
|
"copied" = "已複製"
|
||||||
"download" = "download"
|
"download" = "下載"
|
||||||
"remark" = "remark"
|
"remark" = "備註"
|
||||||
"enable" = "enable"
|
"enable" = "啟用"
|
||||||
"protocol" = "protocol"
|
"protocol" = "協議"
|
||||||
@@ -253,10 +253,7 @@ func (s *Server) startTask() {
|
|||||||
if checkTime < 2 {
|
if checkTime < 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := s.xrayService.RestartXray()
|
s.xrayService.SetIsNeedRestart(true)
|
||||||
if err != nil {
|
|
||||||
logger.Warning("start xray failed:", err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(time.Second * 5)
|
||||||
@@ -316,7 +313,8 @@ func (s *Server) Start() (err error) {
|
|||||||
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
if certFile != "" || keyFile != "" {
|
if certFile != "" || keyFile != "" {
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
var cert tls.Certificate
|
||||||
|
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
493
x-ui.sh
Normal file
493
x-ui.sh
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
red='\033[0;31m'
|
||||||
|
green='\033[0;32m'
|
||||||
|
yellow='\033[0;33m'
|
||||||
|
plain='\033[0m'
|
||||||
|
|
||||||
|
# check root
|
||||||
|
[[ $EUID -ne 0 ]] && echo -e "${red}错误: ${plain} 必须使用root用户运行此脚本!\n" && exit 1
|
||||||
|
|
||||||
|
# check os
|
||||||
|
if [[ -f /etc/redhat-release ]]; then
|
||||||
|
release="centos"
|
||||||
|
elif cat /etc/issue | grep -Eqi "debian"; then
|
||||||
|
release="debian"
|
||||||
|
elif cat /etc/issue | grep -Eqi "ubuntu"; then
|
||||||
|
release="ubuntu"
|
||||||
|
elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then
|
||||||
|
release="centos"
|
||||||
|
elif cat /proc/version | grep -Eqi "debian"; then
|
||||||
|
release="debian"
|
||||||
|
elif cat /proc/version | grep -Eqi "ubuntu"; then
|
||||||
|
release="ubuntu"
|
||||||
|
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
|
||||||
|
release="centos"
|
||||||
|
else
|
||||||
|
echo -e "${red}未检测到系统版本,请联系脚本作者!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
os_version=""
|
||||||
|
|
||||||
|
# os version
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release)
|
||||||
|
fi
|
||||||
|
if [[ -z "$os_version" && -f /etc/lsb-release ]]; then
|
||||||
|
os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ x"${release}" == x"centos" ]]; then
|
||||||
|
if [[ ${os_version} -le 6 ]]; then
|
||||||
|
echo -e "${red}请使用 CentOS 7 或更高版本的系统!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
elif [[ x"${release}" == x"ubuntu" ]]; then
|
||||||
|
if [[ ${os_version} -lt 16 ]]; then
|
||||||
|
echo -e "${red}请使用 Ubuntu 16 或更高版本的系统!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
elif [[ x"${release}" == x"debian" ]]; then
|
||||||
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
|
echo -e "${red}请使用 Debian 8 或更高版本的系统!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
if [[ $# > 1 ]]; then
|
||||||
|
echo && read -p "$1 [默认$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 "是否重启面板,重启面板也会重启 xray" "y"
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
restart
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
before_show_menu() {
|
||||||
|
echo && echo -n -e "${yellow}按回车返回主菜单: ${plain}" && read temp
|
||||||
|
show_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
install() {
|
||||||
|
bash <(curl -Ls https://blog.sprov.xyz/x-ui.sh)
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
start
|
||||||
|
else
|
||||||
|
start 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
confirm "本功能会强制重装当前最新版,数据不会丢失,是否继续?" "n"
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
echo -e "${red}已取消${plain}"
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
bash <(curl -Ls https://blog.sprov.xyz/x-ui.sh)
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo -e "${green}更新完成,已自动重启面板${plain}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
confirm "确定要卸载面板吗,xray 也会卸载?" "n"
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
systemctl stop x-ui
|
||||||
|
systemctl disable x-ui
|
||||||
|
rm /etc/systemd/system/x-ui.service -f
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl reset-failed
|
||||||
|
rm /etc/x-ui/ -rf
|
||||||
|
rm /usr/local/x-ui/ -rf
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "卸载成功,如果你想删除此脚本,则退出脚本后运行 ${green}rm /usr/bin/x-ui -f${plain} 进行删除"
|
||||||
|
echo ""
|
||||||
|
echo -e "Telegram 群组: ${green}https://t.me/sprov_blog${plain}"
|
||||||
|
echo -e "Github issues: ${green}https://github.com/sprov065/x-ui/issues${plain}"
|
||||||
|
echo -e "博客: ${green}https://blog.sprov.xyz/x-ui${plain}"
|
||||||
|
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_user() {
|
||||||
|
confirm "确定要将用户名和密码重置为 admin 吗" "n"
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
/usr/local/x-ui/x-ui setting -username admin -password admin
|
||||||
|
echo -e "用户名和密码已重置为 ${green}admin${plain},现在请重启面板"
|
||||||
|
confirm_restart
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_config() {
|
||||||
|
confirm "确定要重置所有面板设置吗,账号数据不会丢失,用户名和密码不会改变" "n"
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
/usr/local/x-ui/x-ui setting -reset
|
||||||
|
echo -e "所有面板设置已重置为默认值,现在请重启面板,并使用默认的 ${green}54321${plain} 端口访问面板"
|
||||||
|
confirm_restart
|
||||||
|
}
|
||||||
|
|
||||||
|
set_port() {
|
||||||
|
echo && echo -n -e "输入端口号[1-65535]: " && read port
|
||||||
|
if [[ -z "${port}" ]]; then
|
||||||
|
echo -e "${yellow}已取消${plain}"
|
||||||
|
before_show_menu
|
||||||
|
else
|
||||||
|
/usr/local/x-ui/x-ui setting -port ${port}
|
||||||
|
echo -e "设置端口完毕,现在请重启面板,并使用新设置的端口 ${green}${port}${plain} 访问面板"
|
||||||
|
confirm_restart
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
check_status
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${green}面板已运行,无需再次启动,如需重启请选择重启${plain}"
|
||||||
|
else
|
||||||
|
systemctl start x-ui
|
||||||
|
sleep 2
|
||||||
|
check_status
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo -e "${green}x-ui 启动成功${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}面板启动失败,可能是因为启动时间超过了两秒,请稍后查看日志信息${plain}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
check_status
|
||||||
|
if [[ $? == 1 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${green}面板已停止,无需再次停止${plain}"
|
||||||
|
else
|
||||||
|
systemctl stop x-ui
|
||||||
|
sleep 2
|
||||||
|
check_status
|
||||||
|
if [[ $? == 1 ]]; then
|
||||||
|
echo -e "${green}x-ui 与 xray 停止成功${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}面板停止失败,可能是因为停止时间超过了两秒,请稍后查看日志信息${plain}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
systemctl restart x-ui
|
||||||
|
sleep 2
|
||||||
|
check_status
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo -e "${green}x-ui 与 xray 重启成功${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}面板重启失败,可能是因为启动时间超过了两秒,请稍后查看日志信息${plain}"
|
||||||
|
fi
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
systemctl status x-ui -l
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
systemctl enable x-ui
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo -e "${green}x-ui 设置开机自启成功${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}x-ui 设置开机自启失败${plain}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
systemctl disable x-ui
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo -e "${green}x-ui 取消开机自启成功${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}x-ui 取消开机自启失败${plain}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_log() {
|
||||||
|
journalctl -u x-ui.service -e --no-pager -f
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_bbr() {
|
||||||
|
bash <(curl -L -s https://raw.githubusercontent.com/sprov065/blog/master/bbr.sh)
|
||||||
|
echo ""
|
||||||
|
before_show_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
update_shell() {
|
||||||
|
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/sprov065/x-ui/raw/master/x-ui.sh
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${red}下载脚本失败,请检查本机能否连接 Github${plain}"
|
||||||
|
before_show_menu
|
||||||
|
else
|
||||||
|
chmod +x /usr/bin/x-ui
|
||||||
|
echo -e "${green}升级脚本成功,请重新运行脚本${plain}" && exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 0: running, 1: not running, 2: not installed
|
||||||
|
check_status() {
|
||||||
|
if [[ ! -f /etc/systemd/system/x-ui.service ]]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
temp=$(systemctl status x-ui | 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 x-ui)
|
||||||
|
if [[ x"${temp}" == x"enabled" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1;
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_uninstall() {
|
||||||
|
check_status
|
||||||
|
if [[ $? != 2 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${red}面板已安装,请不要重复安装${plain}"
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_install() {
|
||||||
|
check_status
|
||||||
|
if [[ $? == 2 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${red}请先安装面板${plain}"
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
check_status
|
||||||
|
case $? in
|
||||||
|
0)
|
||||||
|
echo -e "面板状态: ${green}已运行${plain}"
|
||||||
|
show_enable_status
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
echo -e "面板状态: ${yellow}未运行${plain}"
|
||||||
|
show_enable_status
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo -e "面板状态: ${red}未安装${plain}"
|
||||||
|
esac
|
||||||
|
show_xray_status
|
||||||
|
}
|
||||||
|
|
||||||
|
show_enable_status() {
|
||||||
|
check_enabled
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo -e "是否开机自启: ${green}是${plain}"
|
||||||
|
else
|
||||||
|
echo -e "是否开机自启: ${red}否${plain}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_xray_status() {
|
||||||
|
count=$(ps -ef | grep "xray-linux" | grep -v "grep" | wc -l)
|
||||||
|
if [[ count -ne 0 ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_xray_status() {
|
||||||
|
check_xray_status
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
echo -e "xray 状态: ${green}运行${plain}"
|
||||||
|
else
|
||||||
|
echo -e "xray 状态: ${red}未运行${plain}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_usage() {
|
||||||
|
echo "x-ui 管理脚本使用方法: "
|
||||||
|
echo "------------------------------------------"
|
||||||
|
echo "x-ui - 显示管理菜单 (功能更多)"
|
||||||
|
echo "x-ui start - 启动 x-ui 面板"
|
||||||
|
echo "x-ui stop - 停止 x-ui 面板"
|
||||||
|
echo "x-ui restart - 重启 x-ui 面板"
|
||||||
|
echo "x-ui status - 查看 x-ui 状态"
|
||||||
|
echo "x-ui enable - 设置 x-ui 开机自启"
|
||||||
|
echo "x-ui disable - 取消 x-ui 开机自启"
|
||||||
|
echo "x-ui log - 查看 x-ui 日志"
|
||||||
|
echo "x-ui update - 更新 x-ui 面板"
|
||||||
|
echo "x-ui install - 安装 x-ui 面板"
|
||||||
|
echo "x-ui uninstall - 卸载 x-ui 面板"
|
||||||
|
echo "------------------------------------------"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_menu() {
|
||||||
|
echo -e "
|
||||||
|
${green}x-ui 面板管理脚本${plain}
|
||||||
|
--- https://blog.sprov.xyz/x-ui ---
|
||||||
|
${green}0.${plain} 退出脚本
|
||||||
|
————————————————
|
||||||
|
${green}1.${plain} 安装 x-ui
|
||||||
|
${green}2.${plain} 更新 x-ui
|
||||||
|
${green}3.${plain} 卸载 x-ui
|
||||||
|
————————————————
|
||||||
|
${green}4.${plain} 重置用户名密码
|
||||||
|
${green}5.${plain} 重置面板设置
|
||||||
|
${green}6.${plain} 设置面板端口
|
||||||
|
————————————————
|
||||||
|
${green}7.${plain} 启动 x-ui
|
||||||
|
${green}8.${plain} 停止 x-ui
|
||||||
|
${green}9.${plain} 重启 x-ui
|
||||||
|
${green}10.${plain} 查看 x-ui 状态
|
||||||
|
${green}11.${plain} 查看 x-ui 日志
|
||||||
|
————————————————
|
||||||
|
${green}12.${plain} 设置 x-ui 开机自启
|
||||||
|
${green}13.${plain} 取消 x-ui 开机自启
|
||||||
|
————————————————
|
||||||
|
${green}14.${plain} 一键安装 bbr (最新内核)
|
||||||
|
"
|
||||||
|
show_status
|
||||||
|
echo && read -p "请输入选择 [0-14]: " num
|
||||||
|
|
||||||
|
case "${num}" in
|
||||||
|
0) exit 0
|
||||||
|
;;
|
||||||
|
1) check_uninstall && install
|
||||||
|
;;
|
||||||
|
2) check_install && update
|
||||||
|
;;
|
||||||
|
3) check_install && uninstall
|
||||||
|
;;
|
||||||
|
4) check_install && reset_user
|
||||||
|
;;
|
||||||
|
5) check_install && reset_config
|
||||||
|
;;
|
||||||
|
6) check_install && set_port
|
||||||
|
;;
|
||||||
|
7) check_install && start
|
||||||
|
;;
|
||||||
|
8) check_install && stop
|
||||||
|
;;
|
||||||
|
9) check_install && restart
|
||||||
|
;;
|
||||||
|
10) check_install && status
|
||||||
|
;;
|
||||||
|
11) check_install && show_log
|
||||||
|
;;
|
||||||
|
12) check_install && enable
|
||||||
|
;;
|
||||||
|
13) check_install && disable
|
||||||
|
;;
|
||||||
|
14) install_bbr
|
||||||
|
;;
|
||||||
|
*) echo -e "${red}请输入正确的数字 [0-14]${plain}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if [[ $# > 0 ]]; then
|
||||||
|
case $1 in
|
||||||
|
"start") check_install 0 && start 0
|
||||||
|
;;
|
||||||
|
"stop") check_install 0 && stop 0
|
||||||
|
;;
|
||||||
|
"restart") check_install 0 && restart 0
|
||||||
|
;;
|
||||||
|
"status") check_install 0 && status 0
|
||||||
|
;;
|
||||||
|
"enable") check_install 0 && enable 0
|
||||||
|
;;
|
||||||
|
"disable") check_install 0 && disable 0
|
||||||
|
;;
|
||||||
|
"log") check_install 0 && show_log 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
|
||||||
@@ -1,24 +1,62 @@
|
|||||||
package xray
|
package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"bytes"
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
LogConfig json.RawMessage `json:"log"`
|
LogConfig json_util.RawMessage `json:"log"`
|
||||||
RouterConfig json.RawMessage `json:"routing"`
|
RouterConfig json_util.RawMessage `json:"routing"`
|
||||||
DNSConfig json.RawMessage `json:"dns"`
|
DNSConfig json_util.RawMessage `json:"dns"`
|
||||||
InboundConfigs []InboundConfig `json:"inbounds"`
|
InboundConfigs []InboundConfig `json:"inbounds"`
|
||||||
OutboundConfigs json.RawMessage `json:"outbounds"`
|
OutboundConfigs json_util.RawMessage `json:"outbounds"`
|
||||||
Transport json.RawMessage `json:"transport"`
|
Transport json_util.RawMessage `json:"transport"`
|
||||||
Policy json.RawMessage `json:"policy"`
|
Policy json_util.RawMessage `json:"policy"`
|
||||||
API json.RawMessage `json:"api"`
|
API json_util.RawMessage `json:"api"`
|
||||||
Stats json.RawMessage `json:"stats"`
|
Stats json_util.RawMessage `json:"stats"`
|
||||||
Reverse json.RawMessage `json:"reverse"`
|
Reverse json_util.RawMessage `json:"reverse"`
|
||||||
FakeDNS json.RawMessage `json:"fakeDns"`
|
FakeDNS json_util.RawMessage `json:"fakeDns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) MarshalJSON() ([]byte, error) {
|
func (c *Config) Equals(other *Config) bool {
|
||||||
return json_util.MarshalJSON(c)
|
if len(c.InboundConfigs) != len(other.InboundConfigs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, inbound := range c.InboundConfigs {
|
||||||
|
if !inbound.Equals(&other.InboundConfigs[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.LogConfig, other.LogConfig) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.RouterConfig, other.RouterConfig) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.DNSConfig, other.DNSConfig) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.OutboundConfigs, other.OutboundConfigs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.Transport, other.Transport) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.Policy, other.Policy) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.API, other.API) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.Stats, other.Stats) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.Reverse, other.Reverse) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.FakeDNS, other.FakeDNS) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,41 @@
|
|||||||
package xray
|
package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"bytes"
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InboundConfig struct {
|
type InboundConfig struct {
|
||||||
Listen json.RawMessage `json:"listen"` // listen 不能为空字符串
|
Listen json_util.RawMessage `json:"listen"` // listen 不能为空字符串
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
Settings json.RawMessage `json:"settings"`
|
Settings json_util.RawMessage `json:"settings"`
|
||||||
StreamSettings json.RawMessage `json:"streamSettings"`
|
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Sniffing json.RawMessage `json:"sniffing"`
|
Sniffing json_util.RawMessage `json:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InboundConfig) MarshalJSON() ([]byte, error) {
|
func (c *InboundConfig) Equals(other *InboundConfig) bool {
|
||||||
return json_util.MarshalJSON(i)
|
if !bytes.Equal(c.Listen, other.Listen) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.Port != other.Port {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.Protocol != other.Protocol {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.Settings, other.Settings) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.StreamSettings, other.StreamSettings) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.Tag != other.Tag {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.Sniffing, other.Sniffing) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,15 +62,15 @@ type process struct {
|
|||||||
version string
|
version string
|
||||||
apiPort int
|
apiPort int
|
||||||
|
|
||||||
xrayConfig *Config
|
config *Config
|
||||||
lines *queue.Queue
|
lines *queue.Queue
|
||||||
exitErr error
|
exitErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProcess(xrayConfig *Config) *process {
|
func newProcess(config *Config) *process {
|
||||||
return &process{
|
return &process{
|
||||||
version: "Unknown",
|
version: "Unknown",
|
||||||
xrayConfig: xrayConfig,
|
config: config,
|
||||||
lines: queue.New(100),
|
lines: queue.New(100),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,9 @@ func (p *process) GetErr() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *process) GetResult() string {
|
func (p *process) GetResult() string {
|
||||||
|
if p.lines.Empty() && p.exitErr != nil {
|
||||||
|
return p.exitErr.Error()
|
||||||
|
}
|
||||||
items, _ := p.lines.TakeUntil(func(item interface{}) bool {
|
items, _ := p.lines.TakeUntil(func(item interface{}) bool {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -108,8 +111,12 @@ func (p *Process) GetAPIPort() int {
|
|||||||
return p.apiPort
|
return p.apiPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Process) GetConfig() *Config {
|
||||||
|
return p.config
|
||||||
|
}
|
||||||
|
|
||||||
func (p *process) refreshAPIPort() {
|
func (p *process) refreshAPIPort() {
|
||||||
for _, inbound := range p.xrayConfig.InboundConfigs {
|
for _, inbound := range p.config.InboundConfigs {
|
||||||
if inbound.Tag == "api" {
|
if inbound.Tag == "api" {
|
||||||
p.apiPort = inbound.Port
|
p.apiPort = inbound.Port
|
||||||
break
|
break
|
||||||
@@ -132,19 +139,25 @@ func (p *process) refreshVersion() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *process) Start() error {
|
func (p *process) Start() (err error) {
|
||||||
if p.IsRunning() {
|
if p.IsRunning() {
|
||||||
return errors.New("xray is already running")
|
return errors.New("xray is already running")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.MarshalIndent(p.xrayConfig, "", " ")
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
p.exitErr = err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(p.config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("生成 xray 配置文件失败: %v", err)
|
||||||
}
|
}
|
||||||
configPath := GetConfigPath()
|
configPath := GetConfigPath()
|
||||||
err = os.WriteFile(configPath, data, fs.ModePerm)
|
err = os.WriteFile(configPath, data, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return common.NewErrorf("写入配置文件失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(GetBinaryPath(), "-c", configPath)
|
cmd := exec.Command(GetBinaryPath(), "-c", configPath)
|
||||||
|
|||||||
Reference in New Issue
Block a user