新增client init命令

This commit is contained in:
songjc 2025-07-15 10:07:38 +08:00
parent 03a3701b47
commit 948f4ce3cc
1 changed files with 366 additions and 0 deletions

View File

@ -0,0 +1,366 @@
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"
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_cert_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_cert_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
}