sync.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package config
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/0xJacky/Nginx-UI/internal/helper"
  8. "github.com/0xJacky/Nginx-UI/internal/logger"
  9. "github.com/0xJacky/Nginx-UI/internal/nginx"
  10. "github.com/0xJacky/Nginx-UI/internal/notification"
  11. "github.com/0xJacky/Nginx-UI/internal/transport"
  12. "github.com/0xJacky/Nginx-UI/model"
  13. "github.com/0xJacky/Nginx-UI/query"
  14. "github.com/0xJacky/Nginx-UI/settings"
  15. "github.com/gin-gonic/gin"
  16. "io"
  17. "net/http"
  18. "os"
  19. "path/filepath"
  20. "strings"
  21. )
  22. type SyncConfigPayload struct {
  23. Name string `json:"name"`
  24. Filepath string `json:"filepath"`
  25. NewFilepath string `json:"new_filepath"`
  26. Content string `json:"content"`
  27. Overwrite bool `json:"overwrite"`
  28. }
  29. func SyncToRemoteServer(c *model.Config, newFilepath string) (err error) {
  30. if c.Filepath == "" || len(c.SyncNodeIds) == 0 {
  31. return
  32. }
  33. nginxConfPath := nginx.GetConfPath()
  34. if !helper.IsUnderDirectory(c.Filepath, nginxConfPath) {
  35. return fmt.Errorf("config: %s is not under the nginx conf path: %s",
  36. c.Filepath, nginxConfPath)
  37. }
  38. if newFilepath != "" && !helper.IsUnderDirectory(newFilepath, nginxConfPath) {
  39. return fmt.Errorf("config: %s is not under the nginx conf path: %s",
  40. c.Filepath, nginxConfPath)
  41. }
  42. currentPath := c.Filepath
  43. if newFilepath != "" {
  44. currentPath = newFilepath
  45. }
  46. configBytes, err := os.ReadFile(currentPath)
  47. if err != nil {
  48. return
  49. }
  50. payload := &SyncConfigPayload{
  51. Name: c.Name,
  52. Filepath: c.Filepath,
  53. NewFilepath: newFilepath,
  54. Content: string(configBytes),
  55. Overwrite: c.SyncOverwrite,
  56. }
  57. payloadBytes, err := json.Marshal(payload)
  58. if err != nil {
  59. return
  60. }
  61. q := query.Environment
  62. envs, _ := q.Where(q.ID.In(c.SyncNodeIds...)).Find()
  63. for _, env := range envs {
  64. go func() {
  65. err := payload.deploy(env, c, payloadBytes)
  66. if err != nil {
  67. logger.Error(err)
  68. }
  69. }()
  70. }
  71. return
  72. }
  73. func SyncRenameOnRemoteServer(origPath, newPath string, syncNodeIds []int) (err error) {
  74. if origPath == "" || newPath == "" || len(syncNodeIds) == 0 {
  75. return
  76. }
  77. nginxConfPath := nginx.GetConfPath()
  78. if !helper.IsUnderDirectory(origPath, nginxConfPath) {
  79. return fmt.Errorf("config: %s is not under the nginx conf path: %s",
  80. origPath, nginxConfPath)
  81. }
  82. if !helper.IsUnderDirectory(newPath, nginxConfPath) {
  83. return fmt.Errorf("config: %s is not under the nginx conf path: %s",
  84. newPath, nginxConfPath)
  85. }
  86. payload := &RenameConfigPayload{
  87. Filepath: origPath,
  88. NewFilepath: newPath,
  89. }
  90. q := query.Environment
  91. envs, _ := q.Where(q.ID.In(syncNodeIds...)).Find()
  92. for _, env := range envs {
  93. go func() {
  94. err := payload.rename(env)
  95. if err != nil {
  96. logger.Error(err)
  97. }
  98. }()
  99. }
  100. return
  101. }
  102. type SyncNotificationPayload struct {
  103. StatusCode int `json:"status_code"`
  104. ConfigName string `json:"config_name"`
  105. EnvName string `json:"env_name"`
  106. RespBody string `json:"resp_body"`
  107. }
  108. func (p *SyncConfigPayload) deploy(env *model.Environment, c *model.Config, payloadBytes []byte) (err error) {
  109. t, err := transport.NewTransport()
  110. if err != nil {
  111. return
  112. }
  113. client := http.Client{
  114. Transport: t,
  115. }
  116. url, err := env.GetUrl("/api/config")
  117. if err != nil {
  118. return
  119. }
  120. req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes))
  121. if err != nil {
  122. return
  123. }
  124. req.Header.Set("X-Node-Secret", env.Token)
  125. resp, err := client.Do(req)
  126. if err != nil {
  127. return
  128. }
  129. defer resp.Body.Close()
  130. respBody, err := io.ReadAll(resp.Body)
  131. if err != nil {
  132. return
  133. }
  134. notificationPayload := &SyncNotificationPayload{
  135. StatusCode: resp.StatusCode,
  136. ConfigName: c.Name,
  137. EnvName: env.Name,
  138. RespBody: string(respBody),
  139. }
  140. notificationPayloadBytes, err := json.Marshal(notificationPayload)
  141. if err != nil {
  142. return
  143. }
  144. if resp.StatusCode != http.StatusOK {
  145. notification.Error("Sync Config Error", string(notificationPayloadBytes))
  146. return
  147. }
  148. notification.Success("Sync Config Success", string(notificationPayloadBytes))
  149. // handle rename
  150. if p.NewFilepath == "" || p.Filepath == p.NewFilepath {
  151. return
  152. }
  153. payload := &RenameConfigPayload{
  154. Filepath: p.Filepath,
  155. NewFilepath: p.NewFilepath,
  156. }
  157. err = payload.rename(env)
  158. return
  159. }
  160. type RenameConfigPayload struct {
  161. Filepath string `json:"filepath"`
  162. NewFilepath string `json:"new_filepath"`
  163. }
  164. type SyncRenameNotificationPayload struct {
  165. StatusCode int `json:"status_code"`
  166. OrigPath string `json:"orig_path"`
  167. NewPath string `json:"new_path"`
  168. EnvName string `json:"env_name"`
  169. RespBody string `json:"resp_body"`
  170. }
  171. func (p *RenameConfigPayload) rename(env *model.Environment) (err error) {
  172. // handle rename
  173. if p.NewFilepath == "" || p.Filepath == p.NewFilepath {
  174. return
  175. }
  176. client := http.Client{
  177. Transport: &http.Transport{
  178. TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify},
  179. },
  180. }
  181. payloadBytes, err := json.Marshal(gin.H{
  182. "base_path": strings.ReplaceAll(filepath.Dir(p.Filepath), nginx.GetConfPath(), ""),
  183. "orig_name": filepath.Base(p.Filepath),
  184. "new_name": filepath.Base(p.NewFilepath),
  185. })
  186. if err != nil {
  187. return
  188. }
  189. url, err := env.GetUrl("/api/config_rename")
  190. if err != nil {
  191. return
  192. }
  193. req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes))
  194. if err != nil {
  195. return
  196. }
  197. req.Header.Set("X-Node-Secret", env.Token)
  198. resp, err := client.Do(req)
  199. if err != nil {
  200. return
  201. }
  202. defer resp.Body.Close()
  203. respBody, err := io.ReadAll(resp.Body)
  204. if err != nil {
  205. return
  206. }
  207. notificationPayload := &SyncRenameNotificationPayload{
  208. StatusCode: resp.StatusCode,
  209. OrigPath: p.Filepath,
  210. NewPath: p.NewFilepath,
  211. EnvName: env.Name,
  212. RespBody: string(respBody),
  213. }
  214. notificationPayloadBytes, err := json.Marshal(notificationPayload)
  215. if err != nil {
  216. return
  217. }
  218. if resp.StatusCode != http.StatusOK {
  219. notification.Error("Rename Remote Config Error", string(notificationPayloadBytes))
  220. return
  221. }
  222. notification.Success("Rename Remote Config Success", string(notificationPayloadBytes))
  223. return
  224. }