You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

init.go 10 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. package cmdline
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/json"
  6. "fmt"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/chzyer/readline"
  13. "github.com/spf13/cobra"
  14. "gitlink.org.cn/cloudream/common/pkgs/logger"
  15. "gitlink.org.cn/cloudream/jcs-pub/client/internal/accesstoken"
  16. clicfg "gitlink.org.cn/cloudream/jcs-pub/client/internal/config"
  17. "gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
  18. "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader"
  19. "gitlink.org.cn/cloudream/jcs-pub/client/internal/http"
  20. mntcfg "gitlink.org.cn/cloudream/jcs-pub/client/internal/mount/config"
  21. "gitlink.org.cn/cloudream/jcs-pub/client/internal/ticktock"
  22. corrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/coordinator"
  23. hubrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/hub"
  24. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/sysevent"
  25. )
  26. var confs_file = path.Join("confs", "client.config.json")
  27. var cli_certs_path = path.Join("confs", "cli_certs")
  28. var pub_certs_path = path.Join("confs", "pub_certs")
  29. var ca_key_file = "ca_key.pem"
  30. var ca_cert_file = "ca_cert.pem"
  31. var client_cert_file = "client_cert.pem"
  32. func init() {
  33. cmd := cobra.Command{
  34. Use: "init",
  35. Short: "initialize client configuration",
  36. Run: func(c *cobra.Command, args []string) {
  37. init2()
  38. },
  39. }
  40. RootCmd.AddCommand(&cmd)
  41. }
  42. func getPath() (exePath string, dirPath string, err error) {
  43. exePath, err = os.Executable()
  44. if err != nil {
  45. return "", "", fmt.Errorf("获取执行路径失败: %w", err)
  46. }
  47. dirPath = filepath.Dir(exePath)
  48. return exePath, dirPath, nil
  49. }
  50. func init2() error {
  51. rl, err := readline.New("> ")
  52. if err != nil {
  53. fmt.Printf("初始化命令行失败: %v\n", err)
  54. return err
  55. }
  56. defer rl.Close()
  57. // 1. 检查配置文件是否存在
  58. _, dirPath, err := getPath()
  59. if err != nil {
  60. return err
  61. }
  62. configFilePath := filepath.Join(dirPath, confs_file)
  63. cliCertsPath := filepath.Join(dirPath, cli_certs_path)
  64. pubCertsPath := filepath.Join(dirPath, pub_certs_path)
  65. _, err = os.Stat(configFilePath)
  66. if err == nil {
  67. fmt.Println("\033[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
  68. fmt.Println("\033[33m⚠ 配置文件已存在!重新初始化会覆盖原配置文件\033[0m")
  69. fmt.Println("\033[33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
  70. checkLoop:
  71. for {
  72. // 2. 配置文件存在,询问是否覆盖
  73. rl.SetPrompt("\033[36m是否继续?(y/n): \033[0m")
  74. overwrite, err := rl.Readline()
  75. if err != nil {
  76. return err
  77. }
  78. switch strings.ToLower(strings.TrimSpace(overwrite)) {
  79. case "y", "yes":
  80. break checkLoop
  81. case "n", "no":
  82. fmt.Printf("\033[32m已保留原有配置文件,退出初始化\033[0m\n")
  83. return nil
  84. default:
  85. fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m")
  86. }
  87. }
  88. }
  89. var cfg clicfg.Config
  90. mysqlLoop:
  91. for {
  92. // 3. 询问数据库配置
  93. rl.SetPrompt("\033[36m请输入Mysql连接地址(例如127.0.0.1:3306): \033[0m")
  94. dbAddress, err := rl.Readline()
  95. if err != nil {
  96. return err
  97. }
  98. rl.SetPrompt("\033[36m请输入Mysql用户名(例如root): \033[0m")
  99. dbAccount, err := rl.Readline()
  100. if err != nil {
  101. return err
  102. }
  103. dbPasswordBytes, err := rl.ReadPassword("\033[36m请输入Mysql密码(例如123456): \033[0m")
  104. if err != nil {
  105. return err
  106. }
  107. dbPassword := string(dbPasswordBytes)
  108. rl.SetPrompt("\033[36m请输入Mysql数据库名称(例如cloudream): \033[0m")
  109. dbName, err := rl.Readline()
  110. if err != nil {
  111. return err
  112. }
  113. cfg.DB = db.Config{
  114. Address: dbAddress,
  115. Account: dbAccount,
  116. Password: dbPassword,
  117. DatabaseName: dbName,
  118. }
  119. rl.SetPrompt("\033[36m是否测试数据库连接?(y/n): \033[0m")
  120. needTest, err := rl.Readline()
  121. if err != nil {
  122. return err
  123. }
  124. switch strings.ToLower(strings.TrimSpace(needTest)) {
  125. case "y", "yes":
  126. // 4. 测试数据库连接
  127. fmt.Printf("\033[33m正在测试数据库连接...\033[0m\n")
  128. testDB, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s",
  129. dbAccount,
  130. dbPassword,
  131. dbAddress,
  132. dbName))
  133. if err != nil {
  134. fmt.Printf("\033[31m连接创建失败: %v\033[0m\n", err)
  135. testDB.Close()
  136. } else {
  137. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  138. defer cancel()
  139. err = testDB.PingContext(ctx)
  140. if err != nil {
  141. fmt.Printf("\033[31m数据库连接测试失败: %v\033[0m\n", err)
  142. testDB.Close()
  143. } else {
  144. fmt.Printf("\033[32m✓ 数据库连接测试成功!\033[0m\n\n")
  145. testDB.Close()
  146. break mysqlLoop
  147. }
  148. }
  149. rl.SetPrompt("\033[36m连接测试失败,是否重新输入配置?(y/n): \033[0m")
  150. retry, err := rl.Readline()
  151. if err != nil {
  152. return err
  153. }
  154. if strings.ToLower(retry) != "y" {
  155. fmt.Printf("\033[33m警告: 数据库连接尚未验证,将使用当前配置,如需后续更改,请查看配置文件client.config.json\033[0m\n")
  156. break mysqlLoop
  157. }
  158. case "n", "no":
  159. fmt.Printf("\033[33m警告: 数据库连接未测试,将直接使用当前配置,如需后续更改,请查看配置文件client.config.json\033[0m\n")
  160. break mysqlLoop
  161. default:
  162. fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m")
  163. }
  164. }
  165. // 5. 询问证书配置
  166. rl.SetPrompt("\033[36m请输入HTTP API监听地址(例如127.0.0.1:3306): \033[0m")
  167. listen, err := rl.Readline()
  168. if err != nil {
  169. return err
  170. }
  171. // 6. 生成证书
  172. err = os.MkdirAll(cliCertsPath, 0755)
  173. if err != nil {
  174. return err
  175. }
  176. keyFilePath := filepath.Join(cliCertsPath, ca_key_file)
  177. certFilePath := filepath.Join(cliCertsPath, ca_cert_file)
  178. certRoot(cliCertsPath)
  179. certServer(certFilePath, keyFilePath, cliCertsPath)
  180. certClient(certFilePath, keyFilePath, cliCertsPath)
  181. cfg.HTTP = &http.ConfigJSON{
  182. Enabled: true,
  183. Listen: listen,
  184. UserSpaceID: 0,
  185. RootCA: path.Join(cli_certs_path, "ca_cert.pem"),
  186. ServerCert: path.Join(cli_certs_path, "server_cert.pem"),
  187. ServerKey: path.Join(cli_certs_path, "server_key.pem"),
  188. ClientCerts: []string{path.Join(cli_certs_path, "client_cert.pem")},
  189. MaxBodySize: 5242880,
  190. }
  191. cloudLoop:
  192. for {
  193. // 7. 填写云际基础设施配置
  194. rl.SetPrompt("\033[36m是否连接云际存储基础设施?(y/n): \033[0m")
  195. isConnect, err := rl.Readline()
  196. if err != nil {
  197. return err
  198. }
  199. switch strings.ToLower(strings.TrimSpace(isConnect)) {
  200. case "y", "yes":
  201. rl.SetPrompt("\033[36m请输入Coordinator地址: \033[0m")
  202. coorAddress, err := rl.Readline()
  203. if err != nil {
  204. return err
  205. }
  206. cfg.CoordinatorRPC = corrpc.PoolConfigJSON{
  207. Address: coorAddress,
  208. RootCA: path.Join(".", pub_certs_path, ca_cert_file),
  209. ClientCert: path.Join(".", pub_certs_path, client_cert_file),
  210. ClientKey: path.Join(".", pub_certs_path, client_cert_file),
  211. }
  212. cfg.HubRPC = hubrpc.PoolConfigJSON{
  213. RootCA: path.Join(".", pub_certs_path, ca_cert_file),
  214. ClientCert: path.Join(".", pub_certs_path, client_cert_file),
  215. ClientKey: path.Join(".", pub_certs_path, client_cert_file),
  216. }
  217. rl.SetPrompt("\033[36m请输入账户名(Account): \033[0m")
  218. account, err := rl.Readline()
  219. if err != nil {
  220. return err
  221. }
  222. passwordBytes, err := rl.ReadPassword("\033[36m请输入密码(Password): \033[0m")
  223. if err != nil {
  224. return err
  225. }
  226. password := string(passwordBytes)
  227. cfg.AccessToken = &accesstoken.Config{
  228. Account: account,
  229. Password: password,
  230. }
  231. fmt.Printf("\033[33m注意:请将JCS-pub证书文件放置于 %s 目录下\033[0m\n", pubCertsPath)
  232. err = os.MkdirAll(pubCertsPath, 0755)
  233. if err != nil {
  234. return err
  235. }
  236. break cloudLoop
  237. case "n", "no":
  238. cfg.CoordinatorRPC.Address = "127.0.0.1:5009"
  239. cfg.AccessToken = &accesstoken.Config{}
  240. break cloudLoop
  241. default:
  242. fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m")
  243. }
  244. }
  245. // 8. 生成配置文件
  246. err = saveConfig(&cfg, configFilePath)
  247. if err != nil {
  248. fmt.Printf("\033[31m保存配置文件失败: %v\033[0m\n", err)
  249. return err
  250. }
  251. fmt.Printf("\033[32m配置文件已生成: %s\033[0m\n", configFilePath)
  252. dbLoop:
  253. for {
  254. // 9. 询问是否生成库表结构
  255. rl.SetPrompt("\033[36m是否生成数据库表?(y/n): \033[0m")
  256. isCreate, err := rl.Readline()
  257. if err != nil {
  258. return err
  259. }
  260. switch strings.ToLower(strings.TrimSpace(isCreate)) {
  261. case "y", "yes":
  262. // 10. 创建库表结构
  263. migrate(configFilePath)
  264. break dbLoop
  265. case "n", "no":
  266. fmt.Println("\033[33m请自行创建数据库表,如需更改配置,请查看配置文件client.config.json \033[0m")
  267. break dbLoop
  268. default:
  269. fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m")
  270. }
  271. }
  272. return nil
  273. }
  274. func saveConfig(cfg *clicfg.Config, configPath string) error {
  275. cfg.Logger = logger.Config{
  276. Level: "info",
  277. Output: "file",
  278. OutputFileName: "client.log",
  279. OutputDirectory: path.Join(".", "logs"),
  280. }
  281. cfg.SysEvent = sysevent.Config{
  282. Enabled: false,
  283. Address: "127.0.0.1:5672",
  284. Account: "cloudream",
  285. Password: "123456",
  286. VHost: "/",
  287. Exchange: "SysEvent",
  288. Queue: "SysEvent",
  289. }
  290. cfg.Connectivity.TestInterval = 300
  291. cfg.Downloader = downloader.Config{
  292. MaxStripCacheCount: 100,
  293. ECStripPrefetchCount: 1,
  294. }
  295. cfg.DownloadStrategy.HighLatencyHubMs = 35
  296. cfg.TickTock = ticktock.Config{
  297. ECFileSizeThreshold: 5242880,
  298. AccessStatHistoryWeight: 0.8,
  299. }
  300. cfg.Mount = &mntcfg.Config{
  301. Enabled: false,
  302. AttrTimeout: time.Second * 10,
  303. UploadPendingTime: time.Second * 30,
  304. CacheActiveTime: time.Minute * 1,
  305. CacheExpireTime: time.Minute * 1,
  306. ScanDataDirInterval: time.Minute * 10,
  307. }
  308. configData, err := json.MarshalIndent(cfg, "", " ")
  309. if err != nil {
  310. return fmt.Errorf("序列化配置失败: %w", err)
  311. }
  312. configData = append(configData, '\n')
  313. err = os.WriteFile(configPath, configData, 0644)
  314. if err != nil {
  315. return fmt.Errorf("写入配置文件失败: %w", err)
  316. }
  317. return nil
  318. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。