Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev: finished mount_all_user_storage_client #11

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/bin_sshfs.go
Original file line number Diff line number Diff line change
@@ -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
}
30 changes: 21 additions & 9 deletions app/job_get_all_user_storage_link_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package app

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
Expand All @@ -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,
})
}

Expand Down
196 changes: 191 additions & 5 deletions app/job_mount_all_user_storage.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand All @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions app/job_sshfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var ModJobSshFs ModJobSshFsStruct
// Usage:
// telego sshfs --remotepath {} --localpath {}

type sshFsJob struct {
type sshFsArgv struct {
remotePath string
localPath string
}
Expand All @@ -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")
Expand All @@ -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")
}
Expand Down
15 changes: 15 additions & 0 deletions util/main_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
RcloneSyncDirOrFileToDir(localPath, fmt.Sprintf("%s:%s", MainNodeRcloneName, remotePath))
}

var MainNodeFileServerURL = fmt.Sprintf("http://%s:8003", MainNodeIp)

Check failure on line 68 in util/main_node.go

View workflow job for this annotation

GitHub Actions / build

undefined: MainNodeIp

Check failure on line 68 in util/main_node.go

View workflow job for this annotation

GitHub Actions / build

undefined: MainNodeIp

type MainNodeConfWriter struct{}

Expand Down Expand Up @@ -141,6 +141,8 @@
}
}

// image_uploader_url

type PubConfTypeImgUploaderUrl struct{}

var _ PubConfType = PubConfTypeImgUploaderUrl{}
Expand All @@ -153,6 +155,19 @@
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
Expand Down Expand Up @@ -352,7 +367,7 @@
if cacheFileServerAccessible == nil {
cacheFileServerAccessible = &cacheFileServerAccessibleStruct{
accessible: NewCheckURLAccessibilityBuilder().
SetURL("http://"+MainNodeIp+":8003").

Check failure on line 370 in util/main_node.go

View workflow job for this annotation

GitHub Actions / build

undefined: MainNodeIp

Check failure on line 370 in util/main_node.go

View workflow job for this annotation

GitHub Actions / build

undefined: MainNodeIp
CheckAccessibility() == nil,
}
}
Expand Down
2 changes: 1 addition & 1 deletion util/run_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading