package cmdline import ( "context" "database/sql" "encoding/json" "fmt" "os" "path" "path/filepath" "strings" "time" "github.com/chzyer/readline" "github.com/spf13/cobra" "gitlink.org.cn/cloudream/common/pkgs/logger" "gitlink.org.cn/cloudream/jcs-pub/client/internal/accesstoken" clicfg "gitlink.org.cn/cloudream/jcs-pub/client/internal/config" "gitlink.org.cn/cloudream/jcs-pub/client/internal/db" "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader" "gitlink.org.cn/cloudream/jcs-pub/client/internal/http" mntcfg "gitlink.org.cn/cloudream/jcs-pub/client/internal/mount/config" "gitlink.org.cn/cloudream/jcs-pub/client/internal/ticktock" corrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/coordinator" hubrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/hub" "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/sysevent" ) var confs_file = path.Join("confs", "client.config.json") var cli_certs_path = path.Join("confs", "cli_certs") var pub_certs_path = path.Join("confs", "pub_certs") var ca_key_file = "ca_key.pem" var ca_cert_file = "ca_cert.pem" var client_cert_file = "client_cert.pem" var client_key_file = "client_key.pem" func init() { cmd := cobra.Command{ Use: "init", Short: "initialize client configuration", Run: func(c *cobra.Command, args []string) { init2() }, } RootCmd.AddCommand(&cmd) } func getPath() (exePath string, dirPath string, err error) { exePath, err = os.Executable() if err != nil { return "", "", fmt.Errorf("获取执行路径失败: %w", err) } dirPath = filepath.Dir(exePath) return exePath, dirPath, nil } func init2() error { rl, err := readline.New("> ") if err != nil { fmt.Printf("初始化命令行失败: %v\n", err) return err } defer rl.Close() // 1. 检查配置文件是否存在 _, dirPath, err := getPath() if err != nil { return err } configFilePath := filepath.Join(dirPath, confs_file) cliCertsPath := filepath.Join(dirPath, cli_certs_path) pubCertsPath := filepath.Join(dirPath, pub_certs_path) _, err = os.Stat(configFilePath) if err == nil { fmt.Println("\033[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m") fmt.Println("\033[33m⚠ 配置文件已存在!重新初始化会覆盖原配置文件\033[0m") fmt.Println("\033[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m") checkLoop: for { // 2. 配置文件存在,询问是否覆盖 rl.SetPrompt("\033[36m是否继续?(y/n): \033[0m") overwrite, err := rl.Readline() if err != nil { return err } switch strings.ToLower(strings.TrimSpace(overwrite)) { case "y", "yes": break checkLoop case "n", "no": fmt.Printf("\033[32m已保留原有配置文件,退出初始化\033[0m\n") return nil default: fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") } } } var cfg clicfg.Config mysqlLoop: for { // 3. 询问数据库配置 rl.SetPrompt("\033[36m请输入Mysql连接地址(例如127.0.0.1:3306): \033[0m") dbAddress, err := rl.Readline() if err != nil { return err } rl.SetPrompt("\033[36m请输入Mysql用户名(例如root): \033[0m") dbAccount, err := rl.Readline() if err != nil { return err } dbPasswordBytes, err := rl.ReadPassword("\033[36m请输入Mysql密码(例如123456): \033[0m") if err != nil { return err } dbPassword := string(dbPasswordBytes) rl.SetPrompt("\033[36m请输入Mysql数据库名称(例如cloudream): \033[0m") dbName, err := rl.Readline() if err != nil { return err } cfg.DB = db.Config{ Address: dbAddress, Account: dbAccount, Password: dbPassword, DatabaseName: dbName, } rl.SetPrompt("\033[36m是否测试数据库连接?(y/n): \033[0m") needTest, err := rl.Readline() if err != nil { return err } switch strings.ToLower(strings.TrimSpace(needTest)) { case "y", "yes": // 4. 测试数据库连接 fmt.Printf("\033[33m正在测试数据库连接...\033[0m\n") testDB, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s", dbAccount, dbPassword, dbAddress, dbName)) if err != nil { fmt.Printf("\033[31m连接创建失败: %v\033[0m\n", err) testDB.Close() } else { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() err = testDB.PingContext(ctx) if err != nil { fmt.Printf("\033[31m数据库连接测试失败: %v\033[0m\n", err) testDB.Close() } else { fmt.Printf("\033[32m✓ 数据库连接测试成功!\033[0m\n\n") testDB.Close() break mysqlLoop } } rl.SetPrompt("\033[36m连接测试失败,是否重新输入配置?(y/n): \033[0m") retry, err := rl.Readline() if err != nil { return err } if strings.ToLower(retry) != "y" { fmt.Printf("\033[33m警告: 数据库连接尚未验证,将使用当前配置,如需后续更改,请查看配置文件client.config.json\033[0m\n") break mysqlLoop } case "n", "no": fmt.Printf("\033[33m警告: 数据库连接未测试,将直接使用当前配置,如需后续更改,请查看配置文件client.config.json\033[0m\n") break mysqlLoop default: fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") } } // 5. 询问证书配置 rl.SetPrompt("\033[36m请输入HTTP API监听地址(例如127.0.0.1:3306): \033[0m") listen, err := rl.Readline() if err != nil { return err } // 6. 生成证书 err = os.MkdirAll(cliCertsPath, 0755) if err != nil { return err } keyFilePath := filepath.Join(cliCertsPath, ca_key_file) certFilePath := filepath.Join(cliCertsPath, ca_cert_file) certRoot(cliCertsPath) certServer(certFilePath, keyFilePath, cliCertsPath) certClient(certFilePath, keyFilePath, cliCertsPath) cfg.HTTP = &http.ConfigJSON{ Enabled: true, Listen: listen, UserSpaceID: 0, RootCA: path.Join(cli_certs_path, "ca_cert.pem"), ServerCert: path.Join(cli_certs_path, "server_cert.pem"), ServerKey: path.Join(cli_certs_path, "server_key.pem"), ClientCerts: []string{path.Join(cli_certs_path, "client_cert.pem")}, MaxBodySize: 5242880, } cloudLoop: for { // 7. 填写云际基础设施配置 rl.SetPrompt("\033[36m是否连接云际存储基础设施?(y/n): \033[0m") isConnect, err := rl.Readline() if err != nil { return err } switch strings.ToLower(strings.TrimSpace(isConnect)) { case "y", "yes": rl.SetPrompt("\033[36m请输入Coordinator地址: \033[0m") coorAddress, err := rl.Readline() if err != nil { return err } cfg.CoordinatorRPC = corrpc.PoolConfigJSON{ Address: coorAddress, RootCA: path.Join(".", pub_certs_path, ca_cert_file), ClientCert: path.Join(".", pub_certs_path, client_cert_file), ClientKey: path.Join(".", pub_certs_path, client_key_file), } cfg.HubRPC = hubrpc.PoolConfigJSON{ RootCA: path.Join(".", pub_certs_path, ca_cert_file), ClientCert: path.Join(".", pub_certs_path, client_cert_file), ClientKey: path.Join(".", pub_certs_path, client_key_file), } rl.SetPrompt("\033[36m请输入账户名(Account): \033[0m") account, err := rl.Readline() if err != nil { return err } passwordBytes, err := rl.ReadPassword("\033[36m请输入密码(Password): \033[0m") if err != nil { return err } password := string(passwordBytes) cfg.AccessToken = &accesstoken.Config{ Account: account, Password: password, } fmt.Printf("\033[33m注意:请将JCS-pub证书文件放置于 %s 目录下\033[0m\n", pubCertsPath) err = os.MkdirAll(pubCertsPath, 0755) if err != nil { return err } break cloudLoop case "n", "no": cfg.CoordinatorRPC.Address = "127.0.0.1:5009" cfg.AccessToken = &accesstoken.Config{} break cloudLoop default: fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") } } // 8. 生成配置文件 err = saveConfig(&cfg, configFilePath) if err != nil { fmt.Printf("\033[31m保存配置文件失败: %v\033[0m\n", err) return err } fmt.Printf("\033[32m配置文件已生成: %s\033[0m\n", configFilePath) dbLoop: for { // 9. 询问是否生成库表结构 rl.SetPrompt("\033[36m是否生成数据库表?(y/n): \033[0m") isCreate, err := rl.Readline() if err != nil { return err } switch strings.ToLower(strings.TrimSpace(isCreate)) { case "y", "yes": // 10. 创建库表结构 migrate(configFilePath) break dbLoop case "n", "no": fmt.Println("\033[33m请自行创建数据库表,如需更改配置,请查看配置文件client.config.json \033[0m") break dbLoop default: fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") } } return nil } func saveConfig(cfg *clicfg.Config, configPath string) error { cfg.Logger = logger.Config{ Level: "info", Output: "file", OutputFileName: "client.log", OutputDirectory: path.Join(".", "logs"), } cfg.SysEvent = sysevent.Config{ Enabled: false, Address: "127.0.0.1:5672", Account: "cloudream", Password: "123456", VHost: "/", Exchange: "SysEvent", Queue: "SysEvent", } cfg.Connectivity.TestInterval = 300 cfg.Downloader = downloader.Config{ MaxStripCacheCount: 100, ECStripPrefetchCount: 1, } cfg.DownloadStrategy.HighLatencyHubMs = 35 cfg.TickTock = ticktock.Config{ ECFileSizeThreshold: 5242880, AccessStatHistoryWeight: 0.8, } cfg.Mount = &mntcfg.Config{ Enabled: false, AttrTimeout: time.Second * 10, UploadPendingTime: time.Second * 30, CacheActiveTime: time.Minute * 1, CacheExpireTime: time.Minute * 1, ScanDataDirInterval: time.Minute * 10, } configData, err := json.MarshalIndent(cfg, "", " ") if err != nil { return fmt.Errorf("序列化配置失败: %w", err) } configData = append(configData, '\n') err = os.WriteFile(configPath, configData, 0644) if err != nil { return fmt.Errorf("写入配置文件失败: %w", err) } return nil }