From 2c58412e841c6f85872d48494ec4ff671f26d3d2 Mon Sep 17 00:00:00 2001 From: peacewang017 Date: Fri, 17 Jan 2025 16:00:19 +0800 Subject: [PATCH] dev: finished mount_all_user_storage_client --- app/bin_sshfs.go | 22 +++ app/job_get_all_user_storage_link_server.go | 30 ++- app/job_mount_all_user_storage.go | 196 +++++++++++++++++++- app/job_sshfs.go | 6 +- util/main_node.go | 15 ++ util/run_cmd.go | 2 +- 6 files changed, 253 insertions(+), 18 deletions(-) create mode 100644 app/bin_sshfs.go diff --git a/app/bin_sshfs.go b/app/bin_sshfs.go new file mode 100644 index 0000000..300fceb --- /dev/null +++ b/app/bin_sshfs.go @@ -0,0 +1,22 @@ +package app + +import "os/exec" + +type BinManagerSshFs struct{} + +func (k BinManagerSshFs) CheckInstalled() bool { + cmd := exec.Command("sshfs", "version") + err := cmd.Run() + if err != nil { + return false + } + return true +} + +func (k BinManagerSshFs) BinName() string { + return "sshfs" +} + +func (k BinManagerSshFs) SpecInstallFunc() func() error { + return nil +} diff --git a/app/job_get_all_user_storage_link_server.go b/app/job_get_all_user_storage_link_server.go index bb17fbf..7eebed3 100644 --- a/app/job_get_all_user_storage_link_server.go +++ b/app/job_get_all_user_storage_link_server.go @@ -4,6 +4,7 @@ package app import ( "fmt" + "net/http" "github.com/gin-gonic/gin" "github.com/spf13/cobra" @@ -13,24 +14,35 @@ type ModJobGetAllUserStorageLinkServerStruct struct{} var ModJobGetAllUserStorageLinkServer ModJobGetAllUserStorageLinkServerStruct +type UserStorage struct { + rootStorage string // 根据 main_node 上存储名称来,例如 gemini-nm / gemini-sh + subPath []string +} + func (m ModJobGetAllUserStorageLinkServerStruct) JobCmdName() string { return "get-all-user-storage-server" } func (m ModJobGetAllUserStorageLinkServerStruct) handleGetPath(c *gin.Context) { - userName := c.DefaultQuery("username", "") - if userName == "" { - c.JSON(400, gin.H{ - "error": "username is required", + var req GetAllUserStorageLinkRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("Invalid request payload: %v", err), }) return } - // 处理 - - // 未完成 - c.JSON(200, GetAllUserStorageLinkResponse{ - RemoteLinks: []string{"path1", "path2", "path3"}, + // 与 Gemini 交互 + // 鉴权 + // 返回集群信息,集群存储根目录列表, + + // (未完成) + remoteLinks := []struct { + Link string `json:"link"` + Storage string `json:"storage"` + }{} + c.JSON(http.StatusOK, GetAllUserStorageLinkResponse{ + RemoteInfos: remoteLinks, }) } diff --git a/app/job_mount_all_user_storage.go b/app/job_mount_all_user_storage.go index 12bebb1..8f1b596 100644 --- a/app/job_mount_all_user_storage.go +++ b/app/job_mount_all_user_storage.go @@ -1,6 +1,18 @@ package app import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path" + "runtime" + "strings" + "telego/util" + + "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -14,20 +26,194 @@ func (m ModJobMountAllUserStorageStruct) JobCmdName() string { // 请求结构体 type GetAllUserStorageLinkRequest struct { - UserName string `json:"username"` // Telego 用户名 + UserName string `json:"username"` // 第三方平台用户名 + PassWord string `json:"password"` // 第三方平台密码 } // 返回结构体 type GetAllUserStorageLinkResponse struct { - RemoteLinks []string `json:"remote_links"` // 供 SshFs 进行挂载的远程链接 + RemoteInfos []struct { + Link string `json:"link"` // 远程链接 + Storage string `json:"storage"` // 存储信息 + } `json:"remote_infos"` // 用于挂载的远程链接信息 } func (m ModJobMountAllUserStorageStruct) Run() { - // 填入 GetAllUserStorageLinkRequest + var userName, passWord string + + // 登录验证 + userInfoFile := util.WorkspaceDir() + "/config/userinfo" + if _, err := os.Stat(userInfoFile); err == nil { + // // 文件存在,尝试读取文件中的内容 + content, err := os.ReadFile(userInfoFile) + if err != nil { + fmt.Println("ModJobMountAllUserStorageStruct.Run: Error reading userinfo file:", err) + return + } + + // // 解析文件内容,查找 username 和 password + lines := strings.Split(string(content), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) // 去掉行首尾的空格 + if line == "" { + continue + } + if strings.HasPrefix(line, "username:") { + userName = strings.TrimSpace(strings.TrimPrefix(line, "username:")) + } else if strings.HasPrefix(line, "password:") { + passWord = strings.TrimSpace(strings.TrimPrefix(line, "password:")) + } + } + + // // 如果从文件中读取了有效的 username 和 password,则直接使用 + if userName != "" && passWord != "" { + fmt.Println("ModJobMountAllUserStorageStruct.Run: Found credentials in userinfo file.") + } else { + fmt.Println("ModJobMountAllUserStorageStruct.Run: No valid credentials found in userinfo file, using UI for input.") + } + } else { + // // 文件不存在,使用 UI 获取输入 + var ok bool + + ok, userName = util.StartTemporaryInputUI( + color.GreenString("ModJobMountAllUserStorageStruct.Run: Mount 用户存储空间需要鉴权,userName"), + "输入 userName", + "回车确认,ctrl + c 取消", + ) + if !ok { + fmt.Println("User canceled input") + return + } + + ok, passWord = util.StartTemporaryInputUI( + color.GreenString("ModJobMountAllUserStorageStruct.Run: Mount 用户存储空间需要鉴权,passWord"), + "输入 passWord", + "回车确认,ctrl + c 取消", + ) + if !ok { + fmt.Println("User canceled input") + return + } + + if userName == "" || passWord == "" { + fmt.Println("ModJobMountAllUserStorageStruct.Run: Input username or password empty") + return + } + } + + // 初始化 http 请求 + req := GetAllUserStorageLinkRequest{ + UserName: userName, + PassWord: passWord, + } + reqBody, err := json.Marshal(req) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: Error marshalling request: %v", err) + } + + // 执行请求,拿到 AllUserStorageLink + serverUrl, err := (util.MainNodeConfReader{}).ReadPubConf(util.PubConfUserStorageServerUrl{}) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: Get server ip error") + } + httpResp, err := http.Post(path.Join(serverUrl, "/getalluserstoragelink"), "application/json", bytes.NewBuffer(reqBody)) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: Error Getting response") + } + if httpResp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(httpResp.Body) // 读取错误信息 + fmt.Printf("ModJobMountAllUserStorageStruct.Run: unexpected status: %d, %s", httpResp.StatusCode, string(body)) + return + } + var resp GetAllUserStorageLinkResponse + defer httpResp.Body.Close() + decoder := json.NewDecoder(httpResp.Body) + err = decoder.Decode(&resp) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: Error decoding response: %v\n", err) + return + } + + // 选定本地挂载根目录 + ok, localRootStorage := util.StartTemporaryInputUI( + color.GreenString("选定本地挂载根目录"), + "输入可用本地路径", + "回车确认,ctrl + c 取消", + ) + if !ok { + fmt.Println("User canceled input") + return + } + + // 对本地挂载根目录进行检验 + if _, err := os.Stat(localRootStorage); os.IsNotExist(err) { + err := os.Mkdir(localRootStorage, 0755) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: Error creating %s", localRootStorage) + return + } + } else { + // // 检查是否是文件 + info, err := os.Stat(localRootStorage) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: Error opening %s\n", localRootStorage) + return + } + + if !info.IsDir() { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: %s is a file, not a directory\n", localRootStorage) + return + } + + files, err := os.ReadDir(localRootStorage) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: Error opening %s", localRootStorage) + return + } - // 进行 http Get 请求 + if len(files) != 0 { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: %s not empty", localRootStorage) + return + } + } + + // 调用 SshFs / rclone 进行挂载 + // // 检查安装 SshFs 和 Rclone + if runtime.GOOS == "linux" && !(BinManagerSshFs{}).CheckInstalled() { + NewBinManager(BinManagerSshFs{}).MakeSureWith() + } else if runtime.GOOS == "windows" && (BinManagerRclone{}).CheckInstalled() { + NewBinManager(BinManagerRclone{}).MakeSureWith() + } + + // // 分系统挂载 + for _, elem := range resp.RemoteInfos { + if runtime.GOOS == "linux" { + err := ModJobSshFs.doMount(&sshFsArgv{ + remotePath: elem.Link, + localPath: path.Join(localRootStorage, elem.Storage), + }) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: linux mount %s to %s error", elem.Link, path.Join(localRootStorage, elem.Storage)) + } + } else if runtime.GOOS == "windows" { + err := ModJobRclone.doMount(&rcloneMountArgv{ + remotePath: elem.Link, + localPath: path.Join(localRootStorage, elem.Storage), + }) + if err != nil { + fmt.Printf("ModJobMountAllUserStorageStruct.Run: windows mount %s to %s error", elem.Link, path.Join(localRootStorage, elem.Storage)) + } + } + } + + // 写入配置文件 + err = os.WriteFile(userInfoFile, []byte("username: "+userName+"\npassword: "+passWord+"\n"), 0644) + if err != nil { + fmt.Println("ModJobMountAllUserStorageStruct.Run: Error writing to userinfo file:", err) + return + } - // 接收结果,调用 ssh / rclone 进行挂载 + fmt.Println("ModJobMountAllUserStorageStruct.Run: Configuration saved successfully.") } func (m ModJobMountAllUserStorageStruct) ParseJob(mountAllUserStorageCmd *cobra.Command) *cobra.Command { diff --git a/app/job_sshfs.go b/app/job_sshfs.go index 6fc52cd..74737a3 100644 --- a/app/job_sshfs.go +++ b/app/job_sshfs.go @@ -16,7 +16,7 @@ var ModJobSshFs ModJobSshFsStruct // Usage: // telego sshfs --remotepath {} --localpath {} -type sshFsJob struct { +type sshFsArgv struct { remotePath string localPath string } @@ -26,7 +26,7 @@ func (m ModJobSshFsStruct) JobCmdName() string { } func (m ModJobSshFsStruct) ParseJob(sshFsCmd *cobra.Command) *cobra.Command { - job := &sshFsJob{} + job := &sshFsArgv{} // 读入参数 sshFsCmd.Flags().StringVar(&job.remotePath, "remotepath", "", "sshfs mount - remote path") @@ -42,7 +42,7 @@ func (m ModJobSshFsStruct) ParseJob(sshFsCmd *cobra.Command) *cobra.Command { return sshFsCmd } -func (m ModJobSshFsStruct) doMount(job *sshFsJob) error { +func (m ModJobSshFsStruct) doMount(job *sshFsArgv) error { if job.localPath == "" || job.remotePath == "" { return fmt.Errorf("doMount: sshfs mount argument empty") } diff --git a/util/main_node.go b/util/main_node.go index c3ac971..57b0d57 100644 --- a/util/main_node.go +++ b/util/main_node.go @@ -141,6 +141,8 @@ func NewPubConfType(path string) PubConfType { } } +// image_uploader_url + type PubConfTypeImgUploaderUrl struct{} var _ PubConfType = PubConfTypeImgUploaderUrl{} @@ -153,6 +155,19 @@ func (r PubConfTypeImgUploaderUrl) Template() string { return "http://127.0.0.1:8002" } +// user_storage_server_url +type PubConfUserStorageServerUrl struct{} + +var _ PubConfType = PubConfUserStorageServerUrl{} + +func (r PubConfUserStorageServerUrl) PubConfPath() string { + return "user_storage_server_url" +} + +func (r PubConfUserStorageServerUrl) Template() string { + return "http://127.0.0.1:8002" +} + type SecretConfType interface { ConfTypeBase SecretConfPath() string diff --git a/util/run_cmd.go b/util/run_cmd.go index f211981..d36967e 100644 --- a/util/run_cmd.go +++ b/util/run_cmd.go @@ -190,7 +190,7 @@ func (m ModRunCmdStruct) CmdModels() CmdModels { return CmdModels{} } -// 在timeout时间范围内条件不满足,返回error +// 在 timeout 时间范围内条件不满足,返回 error // 满足了条件,继续 func RunCmdWithTimeoutCheck( cmdStr []string, timeout time.Duration, conditionMet func(output string) bool) (*bytes.Buffer, *exec.Cmd, error) {