backup.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. package client
  2. import (
  3. "backup-x/entity"
  4. "backup-x/util"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "log"
  9. "os"
  10. "os/exec"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. )
  16. // backupLooper
  17. type backupLooper struct {
  18. Wg sync.WaitGroup
  19. Tickers []*time.Ticker
  20. }
  21. var bl = &backupLooper{Wg: sync.WaitGroup{}}
  22. // RunLoop backup db loop
  23. func RunLoop() {
  24. conf, err := entity.GetConfigCache()
  25. if err != nil {
  26. return
  27. }
  28. // clear
  29. bl.Tickers = []*time.Ticker{}
  30. for _, backupConf := range conf.BackupConfig {
  31. if !backupConf.NotEmptyProject() {
  32. continue
  33. }
  34. if !backupConf.CheckPeriod() {
  35. log.Println(backupConf.ProjectName + "的周期值不正确")
  36. continue
  37. }
  38. delay := util.GetDelaySeconds(backupConf.StartTime)
  39. ticker := time.NewTicker(delay)
  40. log.Printf("%s项目将在%.1f小时后运行\n", backupConf.ProjectName, delay.Hours())
  41. bl.Wg.Add(1)
  42. go func(backupConf entity.BackupConfig) {
  43. defer bl.Wg.Done()
  44. for {
  45. <-ticker.C
  46. run(conf, backupConf)
  47. ticker.Reset(time.Minute * time.Duration(backupConf.Period))
  48. log.Printf("%s项目将等待%d分钟后循环运行\n", backupConf.ProjectName, backupConf.Period)
  49. }
  50. }(backupConf)
  51. bl.Tickers = append(bl.Tickers, ticker)
  52. }
  53. bl.Wg.Wait()
  54. }
  55. // StopRunLoop
  56. func StopRunLoop() {
  57. for _, ticker := range bl.Tickers {
  58. if ticker != nil {
  59. ticker.Stop()
  60. }
  61. }
  62. }
  63. // RunOnce 运行一次
  64. func RunOnce() {
  65. conf, err := entity.GetConfigCache()
  66. if err != nil {
  67. return
  68. }
  69. for _, backupConf := range conf.BackupConfig {
  70. run(conf, backupConf)
  71. }
  72. }
  73. // run
  74. func run(conf entity.Config, backupConf entity.BackupConfig) {
  75. if backupConf.NotEmptyProject() {
  76. err := prepare(backupConf)
  77. if err != nil {
  78. log.Println(err)
  79. return
  80. }
  81. // backup
  82. outFileName, err := backup(backupConf)
  83. result := entity.BackupResult{ProjectName: backupConf.ProjectName, Result: "失败"}
  84. if err == nil {
  85. // webhook
  86. result.FileName = outFileName.Name()
  87. result.FileSize = fmt.Sprintf("%d MB", outFileName.Size()/1000/1000)
  88. result.Result = "成功"
  89. // send file to s3
  90. go conf.UploadFile(backupConf.GetProjectPath() + string(os.PathSeparator) + outFileName.Name())
  91. }
  92. conf.ExecWebhook(result)
  93. }
  94. }
  95. // prepare
  96. func prepare(backupConf entity.BackupConfig) (err error) {
  97. // create floder
  98. os.MkdirAll(backupConf.GetProjectPath(), 0750)
  99. if !strings.Contains(backupConf.Command, "#{DATE}") {
  100. err = errors.New("项目: " + backupConf.ProjectName + "的备份脚本须包含#{DATE}")
  101. }
  102. return
  103. }
  104. func backup(backupConf entity.BackupConfig) (outFileName os.FileInfo, err error) {
  105. projectName := backupConf.ProjectName
  106. log.Printf("正在备份项目: %s ...", projectName)
  107. todayString := time.Now().Format("2006-01-02_15_04")
  108. shellString := strings.ReplaceAll(backupConf.Command, "#{DATE}", todayString)
  109. // create shell file
  110. shellName := time.Now().Format("shell-2006-01-02-15-04-") + "backup.sh"
  111. shellFile, err := os.Create(backupConf.GetProjectPath() + string(os.PathSeparator) + shellName)
  112. shellFile.Chmod(0700)
  113. if err == nil {
  114. shellFile.WriteString(shellString)
  115. shellFile.Close()
  116. } else {
  117. log.Println("Create file with error: ", err)
  118. }
  119. // run shell file
  120. shell := exec.Command("bash", shellName)
  121. shell.Dir = backupConf.GetProjectPath()
  122. outputBytes, err := shell.CombinedOutput()
  123. if len(outputBytes) > 0 {
  124. log.Printf("<span title=\"%s\">%s 执行shell的输出:鼠标移动此处查看</span>\n", util.EscapeShell(string(outputBytes)), backupConf.ProjectName)
  125. } else {
  126. log.Printf("执行shell的输出为空\n")
  127. }
  128. // execute shell success
  129. if err == nil {
  130. // find backup file by todayString
  131. outFileName, err = findBackupFile(backupConf, todayString)
  132. // check file size
  133. if err != nil {
  134. log.Println(err)
  135. } else if outFileName.Size() >= 200 {
  136. log.Printf("成功备份项目: %s, 文件名: %s\n", projectName, outFileName.Name())
  137. // success, remove shell file
  138. os.Remove(shellFile.Name())
  139. } else {
  140. err = errors.New(projectName + " 备份后的文件大小小于200字节, 当前大小:" + strconv.Itoa(int(outFileName.Size())))
  141. log.Println(err)
  142. }
  143. } else {
  144. err = fmt.Errorf("执行备份shell失败: %s", util.EscapeShell(string(outputBytes)))
  145. log.Println(err)
  146. }
  147. return
  148. }
  149. // find backup file by todayString
  150. func findBackupFile(backupConf entity.BackupConfig, todayString string) (backupFile os.FileInfo, err error) {
  151. files, err := ioutil.ReadDir(backupConf.GetProjectPath())
  152. for _, file := range files {
  153. if strings.Contains(file.Name(), todayString) {
  154. backupFile = file
  155. return
  156. }
  157. }
  158. err = errors.New("不能找到备份后的文件,没有找到包含 " + todayString + " 的文件名")
  159. return
  160. }