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

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

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