diff --git a/.air.toml b/.air.toml index 360ce9e..f726a58 100644 --- a/.air.toml +++ b/.air.toml @@ -6,7 +6,7 @@ tmp_dir = "bin" args_bin = [] bin = "./bin/baihu" cmd = ":" - full_bin = "go run main.go" + full_bin = "go run main.go server" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata", "web", "data", "envs", "configs", "bin"] exclude_file = [] diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..708cb17 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/engigu/baihu-panel/cmd/reposync" + "github.com/engigu/baihu-panel/cmd/resetpwd" +) + +// CommandHandler 定义命令执行函数 +type CommandHandler func(args []string) + +// Handlers 维护了除了 server 之外的命令的执行入口 +var Handlers = map[string]CommandHandler{ + "reposync": reposync.Run, + "resetpwd": resetpwd.Run, +} diff --git a/cmd/resetpwd/resetpwd.go b/cmd/resetpwd/resetpwd.go new file mode 100644 index 0000000..03bb905 --- /dev/null +++ b/cmd/resetpwd/resetpwd.go @@ -0,0 +1,54 @@ +package resetpwd + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/engigu/baihu-panel/internal/bootstrap" + "github.com/engigu/baihu-panel/internal/services" + "github.com/engigu/baihu-panel/internal/utils" +) + +func Run(args []string) { + fmt.Print("此操作将重置 admin 用户的密码,是否继续? (y/N): ") + reader := bufio.NewReader(os.Stdin) + answer, _ := reader.ReadString('\n') + answer = strings.TrimSpace(strings.ToLower(answer)) + + if answer != "y" && answer != "yes" { + fmt.Println("操作已取消。") + return + } + + // 必须初始化环境与数据库才能修改密码 + bootstrap.New() + + userService := services.NewUserService() + adminUser := userService.GetUserByUsername("admin") + if adminUser == nil { + fmt.Println("找不到 admin 用户。") + return + } + + fmt.Print("请输入 admin 用户的新密码 (留空则自动随机生成): ") + inputPwd, _ := reader.ReadString('\n') + newPassword := strings.TrimSpace(inputPwd) + if newPassword == "" { + newPassword = utils.RandomString(12) + fmt.Println("未输入密码,系统已自动生成。") + } + + err := userService.UpdatePassword(adminUser.ID, newPassword) + if err != nil { + fmt.Printf("重置密码失败: %v\n", err) + return + } + + fmt.Println("--------------------------------------------------") + fmt.Println("admin 用户密码已重置成功:") + fmt.Printf("新密码: %s\n", newPassword) + fmt.Println("请妥善保管您的新密码,并登录后及时修改。") + fmt.Println("--------------------------------------------------") +} diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 16020b8..38a2883 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -3,6 +3,8 @@ package bootstrap import ( "fmt" "os" + "path/filepath" + "runtime" "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/database" @@ -42,6 +44,23 @@ func (a *App) initConfig() { if err != nil { return } + + a.setupBaihuBin() +} + +func (a *App) setupBaihuBin() { + binDir := filepath.Join(constant.DataDir, "bin") + _ = os.MkdirAll(binDir, 0755) + + exe, err := os.Executable() + if err == nil { + linkPath := filepath.Join(binDir, "baihu") + if runtime.GOOS == "windows" { + linkPath += ".exe" + } + os.Remove(linkPath) + _ = os.Symlink(exe, linkPath) + } } func (a *App) initDatabase() { diff --git a/internal/constant/commands.go b/internal/constant/commands.go new file mode 100644 index 0000000..0cf6715 --- /dev/null +++ b/internal/constant/commands.go @@ -0,0 +1,23 @@ +package constant + +// CommandInfo 定义了终端可用命令的说明信息 +type CommandInfo struct { + Name string + Description string +} + +// Commands 是系统的可用业务命令说明列表 +var Commands = []CommandInfo{ + // { + // Name: "server", + // Description: "启动后台服务进程", + // }, + { + Name: "reposync", + Description: "同步远程 Git 仓库或文件到本地", + }, + { + Name: "resetpwd", + Description: "重置 admin 用户密码(需要二次确认)", + }, +} diff --git a/internal/controllers/terminal_controller.go b/internal/controllers/terminal_controller.go index 4796f82..83e3148 100644 --- a/internal/controllers/terminal_controller.go +++ b/internal/controllers/terminal_controller.go @@ -112,6 +112,12 @@ func (tc *TerminalController) handlePtyMode(conn *websocket.Conn, userID int) { cmd.Env = append(os.Environ(), "TERM=xterm-256color") + // 注入 baihu 命令环境变量 + if absBinDir, err := filepath.Abs(filepath.Join(constant.DataDir, "bin")); err == nil { + pathStr := absBinDir + string(os.PathListSeparator) + os.Getenv("PATH") + cmd.Env = append(cmd.Env, "PATH="+pathStr) + } + // 注入环境变量 envVars := tc.envService.GetEnvVarsByUserID(userID) for _, env := range envVars { @@ -180,6 +186,13 @@ func (tc *TerminalController) handlePipeMode(conn *websocket.Conn, userID int) { // 注入环境变量 cmd.Env = os.Environ() + + // 注入 baihu 命令环境变量 + if absBinDir, err := filepath.Abs(filepath.Join(constant.DataDir, "bin")); err == nil { + pathStr := absBinDir + string(os.PathListSeparator) + os.Getenv("PATH") + cmd.Env = append(cmd.Env, "PATH="+pathStr) + } + envVars := tc.envService.GetEnvVarsByUserID(userID) for _, env := range envVars { cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", env.Name, env.Value)) @@ -279,3 +292,15 @@ func (tc *TerminalController) ExecuteShellCommand(c *gin.Context) { "output": string(output), }) } + +// GetCommands 获取所有可用的 cmd 列表及说明 +func (tc *TerminalController) GetCommands(c *gin.Context) { + var cmds []map[string]string + for _, cmdInfo := range constant.Commands { + cmds = append(cmds, map[string]string{ + "name": cmdInfo.Name, + "description": cmdInfo.Description, + }) + } + utils.Success(c, cmds) +} diff --git a/internal/router/router.go b/internal/router/router.go index be3fbcf..504e6d0 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -181,6 +181,7 @@ func Setup(c *Controllers) *gin.Engine { // 终端模块 authorized.GET("/terminal/ws", c.Terminal.HandleWebSocket) authorized.POST("/terminal/exec", c.Terminal.ExecuteShellCommand) + authorized.GET("/terminal/cmds", c.Terminal.GetCommands) // 设置中心模块 settings := authorized.Group("/settings") diff --git a/main.go b/main.go index 65c9934..6c5c1a3 100644 --- a/main.go +++ b/main.go @@ -4,25 +4,38 @@ import ( "fmt" "os" - "github.com/engigu/baihu-panel/cmd/reposync" + "github.com/engigu/baihu-panel/cmd" "github.com/engigu/baihu-panel/internal/bootstrap" + "github.com/engigu/baihu-panel/internal/constant" ) +func printHelp() { + fmt.Println("Usage: baihu [arguments]") + fmt.Println("Available commands:") + for _, info := range constant.Commands { + fmt.Printf(" %-12s %s\n", info.Name, info.Description) + } +} + func main() { if len(os.Args) < 2 { + printHelp() + os.Exit(1) + } + + commandName := os.Args[1] + + if commandName == "server" { bootstrap.New().Run() return } - cmd := os.Args[1] - switch cmd { - case "server": - bootstrap.New().Run() - case "reposync": - reposync.Run(os.Args[2:]) - default: - fmt.Printf("Unknown command: %s\n", cmd) - fmt.Println("Available commands: server, reposync") - os.Exit(1) + if handler, ok := cmd.Handlers[commandName]; ok { + handler(os.Args[2:]) + return } + + fmt.Printf("Unknown command: %s\n", commandName) + printHelp() + os.Exit(1) } diff --git a/web/src/api/index.ts b/web/src/api/index.ts index 6108dae..5c1a417 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -256,6 +256,9 @@ export const api = { plugins: () => request('/mise/plugins'), versions: (plugin: string) => request(`/mise/versions?plugin=${plugin}`), verifyCommand: (plugin: string, version: string) => request<{ command: string }>(`/mise/verify-cmd?plugin=${plugin}&version=${version}`) + }, + terminal: { + cmds: () => request<{ name: string, description: string }[]>('/terminal/cmds') } } diff --git a/web/src/views/terminal/Terminal.vue b/web/src/views/terminal/Terminal.vue index f335d61..9508088 100644 --- a/web/src/views/terminal/Terminal.vue +++ b/web/src/views/terminal/Terminal.vue @@ -1,10 +1,22 @@