server.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. package httpserver
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "sync"
  11. "time"
  12. "networktool/internal/auth"
  13. "networktool/internal/config"
  14. "networktool/internal/deviceinfo"
  15. "networktool/internal/logger"
  16. "networktool/internal/model"
  17. applyexecsvc "networktool/internal/network/applyexec"
  18. configreadersvc "networktool/internal/network/configreader"
  19. interfacesvc "networktool/internal/network/interfaces"
  20. netplansvc "networktool/internal/network/netplan"
  21. validatorsvc "networktool/internal/network/validator"
  22. "networktool/internal/systemaction"
  23. "networktool/internal/tasks"
  24. )
  25. type Server struct {
  26. cfg config.Config
  27. log *logger.Logger
  28. deviceSvc *deviceinfo.Service
  29. interfaceSvc *interfacesvc.Service
  30. configSvc *configreadersvc.Service
  31. validatorSvc *validatorsvc.Service
  32. netplanSvc *netplansvc.Service
  33. applySvc *applyexecsvc.Service
  34. taskSvc *tasks.Service
  35. systemSvc *systemaction.Service
  36. confirmMu sync.Mutex
  37. applyControls map[string]applyControl
  38. }
  39. type applyControl struct {
  40. confirm chan struct{}
  41. cancel chan struct{}
  42. }
  43. const applyConfirmationTimeout = 20 * time.Second
  44. func New(cfg config.Config, log *logger.Logger, deviceSvc *deviceinfo.Service, interfaceSvc *interfacesvc.Service, configSvc *configreadersvc.Service, validatorSvc *validatorsvc.Service, netplanSvc *netplansvc.Service, applySvc *applyexecsvc.Service, taskSvc *tasks.Service, systemSvc *systemaction.Service) *Server {
  45. return &Server{cfg: cfg, log: log, deviceSvc: deviceSvc, interfaceSvc: interfaceSvc, configSvc: configSvc, validatorSvc: validatorSvc, netplanSvc: netplanSvc, applySvc: applySvc, taskSvc: taskSvc, systemSvc: systemSvc, applyControls: make(map[string]applyControl)}
  46. }
  47. func (s *Server) Run(ctx context.Context) error {
  48. mux := http.NewServeMux()
  49. mux.Handle("/api/health", auth.Middleware(s.cfg, http.HandlerFunc(s.handleHealth)))
  50. mux.Handle("/api/device/info", auth.Middleware(s.cfg, http.HandlerFunc(s.handleDeviceInfo)))
  51. mux.Handle("/api/network/interfaces", auth.Middleware(s.cfg, http.HandlerFunc(s.handleInterfaces)))
  52. mux.Handle("/api/network/config", auth.Middleware(s.cfg, http.HandlerFunc(s.handleConfig)))
  53. mux.Handle("/api/network/validate", auth.Middleware(s.cfg, http.HandlerFunc(s.handleValidate)))
  54. mux.Handle("/api/network/validate-all", auth.Middleware(s.cfg, http.HandlerFunc(s.handleValidateAll)))
  55. mux.Handle("/api/network/apply", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApply)))
  56. mux.Handle("/api/network/apply-all", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApplyAll)))
  57. mux.Handle("/api/network/apply/confirm", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApplyConfirm)))
  58. mux.Handle("/api/network/apply/cancel", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApplyCancel)))
  59. mux.Handle("/api/network/rollback", auth.Middleware(s.cfg, http.HandlerFunc(s.handleRollback)))
  60. mux.Handle("/api/system/reboot", auth.Middleware(s.cfg, http.HandlerFunc(s.handleReboot)))
  61. mux.Handle("/api/system/shutdown", auth.Middleware(s.cfg, http.HandlerFunc(s.handleShutdown)))
  62. mux.Handle("/api/tasks/", auth.Middleware(s.cfg, http.HandlerFunc(s.handleTaskGet)))
  63. handler := s.withAccessLog(mux)
  64. server := &http.Server{
  65. Addr: fmt.Sprintf("%s:%d", s.cfg.HTTPHost, s.cfg.HTTPPort),
  66. Handler: handler,
  67. ReadHeaderTimeout: 5 * time.Second,
  68. }
  69. go func() {
  70. <-ctx.Done()
  71. shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  72. defer cancel()
  73. _ = server.Shutdown(shutdownCtx)
  74. }()
  75. s.log.Info("http server listening", "addr", server.Addr)
  76. err := server.ListenAndServe()
  77. if err == http.ErrServerClosed {
  78. return nil
  79. }
  80. return err
  81. }
  82. func (s *Server) handleApplyConfirm(w http.ResponseWriter, r *http.Request) {
  83. if r.Method != http.MethodPost {
  84. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  85. return
  86. }
  87. var input model.ConfirmApplyRequest
  88. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  89. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  90. return
  91. }
  92. if input.TaskID == "" {
  93. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 task_id。"}}})
  94. return
  95. }
  96. if !s.confirmApply(input.TaskID) {
  97. writeJSON(w, http.StatusNotFound, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  98. return
  99. }
  100. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "已确认保留配置", Data: map[string]any{"task_id": input.TaskID, "confirmed": true}})
  101. }
  102. func (s *Server) handleApplyCancel(w http.ResponseWriter, r *http.Request) {
  103. if r.Method != http.MethodPost {
  104. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  105. return
  106. }
  107. var input model.ConfirmApplyRequest
  108. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  109. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  110. return
  111. }
  112. if input.TaskID == "" {
  113. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 task_id。"}}})
  114. return
  115. }
  116. if !s.cancelApply(input.TaskID) {
  117. writeJSON(w, http.StatusNotFound, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  118. return
  119. }
  120. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "已取消保留配置,正在回滚", Data: map[string]any{"task_id": input.TaskID, "cancelled": true}})
  121. }
  122. func (s *Server) withAccessLog(next http.Handler) http.Handler {
  123. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  124. started := time.Now()
  125. rw := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK}
  126. next.ServeHTTP(rw, r)
  127. s.log.Info(
  128. "http request completed",
  129. "method", r.Method,
  130. "path", r.URL.Path,
  131. "query", r.URL.RawQuery,
  132. "remote", r.RemoteAddr,
  133. "status", rw.statusCode,
  134. "duration_ms", time.Since(started).Milliseconds(),
  135. )
  136. })
  137. }
  138. type statusRecorder struct {
  139. http.ResponseWriter
  140. statusCode int
  141. }
  142. func (r *statusRecorder) WriteHeader(statusCode int) {
  143. r.statusCode = statusCode
  144. r.ResponseWriter.WriteHeader(statusCode)
  145. }
  146. func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) {
  147. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: map[string]any{"status": "运行中", "server_version": s.cfg.ServerVersion}})
  148. }
  149. func (s *Server) handleDeviceInfo(w http.ResponseWriter, _ *http.Request) {
  150. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: s.deviceSvc.Get()})
  151. }
  152. func (s *Server) handleInterfaces(w http.ResponseWriter, _ *http.Request) {
  153. data, err := s.interfaceSvc.List()
  154. if err != nil {
  155. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}})
  156. return
  157. }
  158. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: data})
  159. }
  160. func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
  161. interfaceName := r.URL.Query().Get("interface")
  162. if interfaceName == "" {
  163. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 interface 参数。"}}})
  164. return
  165. }
  166. if !s.interfaceExists(interfaceName) {
  167. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"目标接口不存在。"}}})
  168. return
  169. }
  170. data, err := s.configSvc.Read(interfaceName)
  171. if err != nil {
  172. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}})
  173. return
  174. }
  175. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: data})
  176. }
  177. func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) {
  178. if r.Method != http.MethodPost {
  179. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  180. return
  181. }
  182. body, err := io.ReadAll(r.Body)
  183. if err != nil {
  184. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体读取失败。"}}})
  185. return
  186. }
  187. var input model.InterfaceConfig
  188. if err := json.Unmarshal(body, &input); err != nil {
  189. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  190. return
  191. }
  192. if input.Interface != "" && !s.interfaceExists(input.Interface) {
  193. result := model.ValidateResponse{Valid: false, Errors: []string{"目标接口不存在。"}, Warnings: []string{}}
  194. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  195. return
  196. }
  197. result := s.validatorSvc.Validate(input)
  198. if !result.Valid {
  199. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  200. return
  201. }
  202. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "校验通过", Data: result})
  203. }
  204. func (s *Server) handleValidateAll(w http.ResponseWriter, r *http.Request) {
  205. if r.Method != http.MethodPost {
  206. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  207. return
  208. }
  209. var input model.InterfaceConfigsRequest
  210. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  211. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  212. return
  213. }
  214. result := s.validateConfigs(input.Configs)
  215. if !result.Valid {
  216. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  217. return
  218. }
  219. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "校验通过", Data: result})
  220. }
  221. func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
  222. if r.Method != http.MethodPost {
  223. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  224. return
  225. }
  226. if !hasRootPrivileges() {
  227. writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Server 未以 root 身份运行,无法写入 netplan 或执行 netplan apply。"}}})
  228. return
  229. }
  230. var input model.InterfaceConfig
  231. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  232. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  233. return
  234. }
  235. if !s.interfaceExists(input.Interface) {
  236. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: model.ValidateResponse{Valid: false, Errors: []string{"目标接口不存在。"}}})
  237. return
  238. }
  239. result := s.validatorSvc.Validate(input)
  240. if !result.Valid {
  241. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  242. return
  243. }
  244. management := s.currentManagementInterface()
  245. if management == "" {
  246. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"未能识别管理接口。"}}})
  247. return
  248. }
  249. task := s.taskSvc.Create()
  250. go s.runApplyTask(task.TaskID, input, management)
  251. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "配置任务已提交", Data: map[string]any{"interface": input.Interface, "task_id": task.TaskID}})
  252. }
  253. func (s *Server) handleApplyAll(w http.ResponseWriter, r *http.Request) {
  254. if r.Method != http.MethodPost {
  255. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  256. return
  257. }
  258. if !hasRootPrivileges() {
  259. writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Server 未以 root 身份运行,无法写入 netplan 或执行 netplan apply。"}}})
  260. return
  261. }
  262. var input model.InterfaceConfigsRequest
  263. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  264. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  265. return
  266. }
  267. result := s.validateConfigs(input.Configs)
  268. if !result.Valid {
  269. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  270. return
  271. }
  272. management := s.currentManagementInterface()
  273. if management == "" {
  274. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"未能识别管理接口。"}}})
  275. return
  276. }
  277. task := s.taskSvc.Create()
  278. go s.runApplyAllTask(task.TaskID, input.Configs, management)
  279. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "配置任务已提交", Data: map[string]any{"task_id": task.TaskID}})
  280. }
  281. func (s *Server) handleRollback(w http.ResponseWriter, r *http.Request) {
  282. if r.Method != http.MethodPost {
  283. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  284. return
  285. }
  286. if !hasRootPrivileges() {
  287. writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Server 未以 root 身份运行,无法恢复 netplan 或执行 netplan apply。"}}})
  288. return
  289. }
  290. var input model.RollbackRequest
  291. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  292. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  293. return
  294. }
  295. filePath, err := s.netplanSvc.FindSingleFile()
  296. if err != nil {
  297. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}})
  298. return
  299. }
  300. backupPath := filePath + ".networktool.bak"
  301. if err := s.netplanSvc.Restore(filePath, backupPath); err != nil {
  302. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 3004, Message: "回滚失败", Data: map[string]string{"error": err.Error()}})
  303. return
  304. }
  305. if err := s.applySvc.Apply(); err != nil {
  306. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 3004, Message: "回滚失败", Data: map[string]string{"error": err.Error()}})
  307. return
  308. }
  309. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "回滚成功", Data: map[string]any{"interface": input.Interface, "rolled_back": true}})
  310. }
  311. func (s *Server) handleTaskGet(w http.ResponseWriter, r *http.Request) {
  312. taskID := strings.TrimPrefix(r.URL.Path, "/api/tasks/")
  313. if taskID == "" {
  314. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 task_id。"}}})
  315. return
  316. }
  317. item, ok := s.taskSvc.Get(taskID)
  318. if !ok {
  319. writeJSON(w, http.StatusNotFound, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  320. return
  321. }
  322. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: item})
  323. }
  324. func (s *Server) handleReboot(w http.ResponseWriter, r *http.Request) {
  325. s.handleSystemAction(w, r, "reboot")
  326. }
  327. func (s *Server) handleShutdown(w http.ResponseWriter, r *http.Request) {
  328. s.handleSystemAction(w, r, "shutdown")
  329. }
  330. func (s *Server) handleSystemAction(w http.ResponseWriter, r *http.Request, action string) {
  331. if r.Method != http.MethodPost {
  332. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  333. return
  334. }
  335. if !hasRootPrivileges() {
  336. writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Server 未以 root 身份运行,无法执行重启或关机。"}}})
  337. return
  338. }
  339. task := s.taskSvc.Create()
  340. go s.runSystemTask(task.TaskID, action)
  341. message := "系统任务已提交"
  342. if action == "reboot" {
  343. message = "重启任务已提交"
  344. } else if action == "shutdown" {
  345. message = "关机任务已提交"
  346. }
  347. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: message, Data: map[string]any{"action": action, "task_id": task.TaskID}})
  348. }
  349. func (s *Server) runApplyTask(taskID string, input model.InterfaceConfig, managementInterface string) {
  350. s.taskSvc.Update(taskID, "running", "validating", "正在校验配置。", false)
  351. result := s.validatorSvc.Validate(input)
  352. if !result.Valid {
  353. s.taskSvc.Update(taskID, "failed", "validating", "配置校验失败。", false)
  354. return
  355. }
  356. filePath, err := s.netplanSvc.FindSingleFile()
  357. if err != nil {
  358. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  359. return
  360. }
  361. s.taskSvc.Update(taskID, "running", "writing_netplan", "正在写入 netplan 配置。", false)
  362. backupPath, err := s.netplanSvc.Backup(filePath)
  363. if err != nil {
  364. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  365. return
  366. }
  367. s.log.Info("preparing to write netplan", "task_id", taskID, "file", filePath, "target_interface", input.Interface, "addresses", formatAddresses(input), "routes", formatRoutes(input), "dns", strings.Join(input.DNS, ","))
  368. if err := s.netplanSvc.Write(filePath, input.Interface, input, managementInterface, s.cfg.MaintenanceCIDR); err != nil {
  369. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  370. return
  371. }
  372. if writtenData, err := os.ReadFile(filePath); err != nil {
  373. s.log.Error("failed to read netplan after write", "task_id", taskID, "file", filePath, "error", err.Error())
  374. } else {
  375. s.log.Info("netplan written", "task_id", taskID, "file", filePath, "content", string(writtenData))
  376. }
  377. control := s.registerApplyControl(taskID)
  378. defer s.unregisterApplyControl(taskID)
  379. s.taskSvc.Update(taskID, "running", "applying", "正在应用 netplan 配置。", false)
  380. if err := s.applySvc.Apply(); err != nil {
  381. s.log.Error("netplan apply failed, restoring netplan file", "task_id", taskID, "file", filePath, "error", err.Error())
  382. _ = s.netplanSvc.Restore(filePath, backupPath)
  383. _ = s.applySvc.Apply()
  384. _ = s.interfaceSvc.EnsureMaintenanceAddress()
  385. s.logNetplanFile(taskID, filePath, "netplan restored after apply failure")
  386. s.taskSvc.Update(taskID, "rolled_back", "rolling_back", fmt.Sprintf("应用 netplan 失败,已自动回滚:%v", err), true)
  387. return
  388. }
  389. s.taskSvc.Update(taskID, "running", "confirming", fmt.Sprintf("配置已应用,请在 %d 秒内确认保留;未确认将自动回滚。", int(applyConfirmationTimeout.Seconds())), false)
  390. select {
  391. case <-control.confirm:
  392. _ = os.Remove(backupPath)
  393. s.taskSvc.Update(taskID, "success", "completed", "配置已应用并由客户端确认保留。", false)
  394. return
  395. case <-control.cancel:
  396. s.rollbackAppliedConfig(taskID, filePath, backupPath, "用户取消保留配置")
  397. return
  398. case <-time.After(applyConfirmationTimeout):
  399. s.rollbackAppliedConfig(taskID, filePath, backupPath, "确认超时")
  400. return
  401. }
  402. }
  403. func (s *Server) runApplyAllTask(taskID string, inputs []model.InterfaceConfig, managementInterface string) {
  404. s.taskSvc.Update(taskID, "running", "validating", "正在校验配置。", false)
  405. result := s.validateConfigs(inputs)
  406. if !result.Valid {
  407. s.taskSvc.Update(taskID, "failed", "validating", "配置校验失败。", false)
  408. return
  409. }
  410. filePath, err := s.netplanSvc.FindSingleFile()
  411. if err != nil {
  412. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  413. return
  414. }
  415. s.taskSvc.Update(taskID, "running", "writing_netplan", "正在写入 netplan 配置。", false)
  416. backupPath, err := s.netplanSvc.Backup(filePath)
  417. if err != nil {
  418. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  419. return
  420. }
  421. if err := s.netplanSvc.WriteMany(filePath, inputs, managementInterface, s.cfg.MaintenanceCIDR); err != nil {
  422. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  423. return
  424. }
  425. if writtenData, err := os.ReadFile(filePath); err != nil {
  426. s.log.Error("failed to read netplan after write", "task_id", taskID, "file", filePath, "error", err.Error())
  427. } else {
  428. s.log.Info("netplan written", "task_id", taskID, "file", filePath, "content", string(writtenData))
  429. }
  430. control := s.registerApplyControl(taskID)
  431. defer s.unregisterApplyControl(taskID)
  432. s.taskSvc.Update(taskID, "running", "applying", "正在应用 netplan 配置。", false)
  433. if err := s.applySvc.Apply(); err != nil {
  434. s.log.Error("netplan apply failed, restoring netplan file", "task_id", taskID, "file", filePath, "error", err.Error())
  435. _ = s.netplanSvc.Restore(filePath, backupPath)
  436. _ = s.applySvc.Apply()
  437. _ = s.interfaceSvc.EnsureMaintenanceAddress()
  438. s.logNetplanFile(taskID, filePath, "netplan restored after apply failure")
  439. s.taskSvc.Update(taskID, "rolled_back", "rolling_back", fmt.Sprintf("应用 netplan 失败,已自动回滚:%v", err), true)
  440. return
  441. }
  442. s.taskSvc.Update(taskID, "running", "confirming", fmt.Sprintf("配置已应用,请在 %d 秒内确认保留;未确认将自动回滚。", int(applyConfirmationTimeout.Seconds())), false)
  443. select {
  444. case <-control.confirm:
  445. _ = os.Remove(backupPath)
  446. s.taskSvc.Update(taskID, "success", "completed", "配置已应用并由客户端确认保留。", false)
  447. return
  448. case <-control.cancel:
  449. s.rollbackAppliedConfig(taskID, filePath, backupPath, "用户取消保留配置")
  450. return
  451. case <-time.After(applyConfirmationTimeout):
  452. s.rollbackAppliedConfig(taskID, filePath, backupPath, "确认超时")
  453. return
  454. }
  455. }
  456. func (s *Server) validateConfigs(inputs []model.InterfaceConfig) model.ValidateResponse {
  457. result := model.ValidateResponse{Valid: true, Warnings: []string{}, Errors: []string{}}
  458. if len(inputs) == 0 {
  459. result.Valid = false
  460. result.Errors = append(result.Errors, "接口配置不能为空。")
  461. return result
  462. }
  463. seen := make(map[string]struct{})
  464. for _, input := range inputs {
  465. name := strings.TrimSpace(input.Interface)
  466. if name == "" {
  467. result.Valid = false
  468. result.Errors = append(result.Errors, "目标接口不能为空。")
  469. continue
  470. }
  471. if _, ok := seen[name]; ok {
  472. result.Valid = false
  473. result.Errors = append(result.Errors, fmt.Sprintf("接口重复:%s", name))
  474. continue
  475. }
  476. seen[name] = struct{}{}
  477. if !s.interfaceExists(name) {
  478. result.Valid = false
  479. result.Errors = append(result.Errors, fmt.Sprintf("目标接口不存在:%s", name))
  480. continue
  481. }
  482. item := s.validatorSvc.Validate(input)
  483. if !item.Valid {
  484. result.Valid = false
  485. }
  486. for _, err := range item.Errors {
  487. result.Errors = append(result.Errors, fmt.Sprintf("%s:%s", name, err))
  488. }
  489. for _, warning := range item.Warnings {
  490. result.Warnings = append(result.Warnings, fmt.Sprintf("%s:%s", name, warning))
  491. }
  492. }
  493. return result
  494. }
  495. func (s *Server) rollbackAppliedConfig(taskID string, filePath string, backupPath string, reason string) {
  496. s.log.Warn("apply confirmation failed, restoring netplan file", "task_id", taskID, "file", filePath, "reason", reason)
  497. _ = s.netplanSvc.Restore(filePath, backupPath)
  498. _ = s.applySvc.Apply()
  499. _ = s.interfaceSvc.EnsureMaintenanceAddress()
  500. s.logNetplanFile(taskID, filePath, "netplan restored after confirmation failure")
  501. s.taskSvc.Update(taskID, "rolled_back", "rolling_back", fmt.Sprintf("%s,已自动回滚。", reason), true)
  502. }
  503. func (s *Server) registerApplyControl(taskID string) applyControl {
  504. control := applyControl{confirm: make(chan struct{}, 1), cancel: make(chan struct{}, 1)}
  505. s.confirmMu.Lock()
  506. s.applyControls[taskID] = control
  507. s.confirmMu.Unlock()
  508. return control
  509. }
  510. func (s *Server) unregisterApplyControl(taskID string) {
  511. s.confirmMu.Lock()
  512. delete(s.applyControls, taskID)
  513. s.confirmMu.Unlock()
  514. }
  515. func (s *Server) confirmApply(taskID string) bool {
  516. s.confirmMu.Lock()
  517. control, ok := s.applyControls[taskID]
  518. s.confirmMu.Unlock()
  519. if !ok {
  520. return false
  521. }
  522. select {
  523. case control.confirm <- struct{}{}:
  524. default:
  525. }
  526. return true
  527. }
  528. func (s *Server) cancelApply(taskID string) bool {
  529. s.confirmMu.Lock()
  530. control, ok := s.applyControls[taskID]
  531. s.confirmMu.Unlock()
  532. if !ok {
  533. return false
  534. }
  535. select {
  536. case control.cancel <- struct{}{}:
  537. default:
  538. }
  539. return true
  540. }
  541. func (s *Server) logNetplanFile(taskID string, filePath string, message string) {
  542. data, err := os.ReadFile(filePath)
  543. if err != nil {
  544. s.log.Error("failed to read netplan file for logging", "task_id", taskID, "file", filePath, "error", err.Error())
  545. return
  546. }
  547. s.log.Info(message, "task_id", taskID, "file", filePath, "content", string(data))
  548. }
  549. func (s *Server) runSystemTask(taskID string, action string) {
  550. detail := "正在发送系统指令。"
  551. if action == "reboot" {
  552. detail = "正在发送重启指令。"
  553. } else if action == "shutdown" {
  554. detail = "正在发送关机指令。"
  555. }
  556. s.taskSvc.Update(taskID, "running", "executing", detail, false)
  557. var err error
  558. switch action {
  559. case "reboot":
  560. err = s.systemSvc.Reboot()
  561. case "shutdown":
  562. err = s.systemSvc.Shutdown()
  563. default:
  564. err = fmt.Errorf("unsupported system action: %s", action)
  565. }
  566. if err != nil {
  567. s.log.Error("system action failed", "action", action, "error", err.Error())
  568. s.taskSvc.Update(taskID, "failed", "executing", err.Error(), false)
  569. return
  570. }
  571. completedDetail := "系统指令已发送。"
  572. if action == "reboot" {
  573. completedDetail = "系统重启指令已发送。"
  574. } else if action == "shutdown" {
  575. completedDetail = "系统关机指令已发送。"
  576. }
  577. s.taskSvc.Update(taskID, "success", "completed", completedDetail, false)
  578. }
  579. func (s *Server) interfaceExists(name string) bool {
  580. data, err := s.interfaceSvc.List()
  581. if err != nil {
  582. return false
  583. }
  584. for _, item := range data.Interfaces {
  585. if item.SystemName == name {
  586. return true
  587. }
  588. }
  589. return false
  590. }
  591. func (s *Server) currentManagementInterface() string {
  592. data, err := s.interfaceSvc.List()
  593. if err != nil {
  594. return ""
  595. }
  596. return data.ManagementInterface
  597. }
  598. func writeJSON(w http.ResponseWriter, status int, payload model.APIResponse) {
  599. w.Header().Set("Content-Type", "application/json")
  600. w.WriteHeader(status)
  601. _ = json.NewEncoder(w).Encode(payload)
  602. }
  603. func hasRootPrivileges() bool {
  604. return os.Geteuid() == 0
  605. }
  606. func formatAddresses(input model.InterfaceConfig) string {
  607. addresses := input.Addresses
  608. if len(addresses) == 0 && strings.TrimSpace(input.IP) != "" {
  609. addresses = []model.InterfaceAddressConfig{{IP: input.IP, Prefix: input.Prefix}}
  610. }
  611. items := make([]string, 0, len(addresses))
  612. for _, address := range addresses {
  613. items = append(items, fmt.Sprintf("%s/%d", strings.TrimSpace(address.IP), address.Prefix))
  614. }
  615. return strings.Join(items, ",")
  616. }
  617. func formatRoutes(input model.InterfaceConfig) string {
  618. routes := input.Routes
  619. if len(routes) == 0 && strings.TrimSpace(input.Gateway) != "" {
  620. routes = []model.InterfaceRouteConfig{{To: "default", Via: input.Gateway}}
  621. }
  622. items := make([]string, 0, len(routes))
  623. for _, route := range routes {
  624. items = append(items, fmt.Sprintf("%s via %s", strings.TrimSpace(route.To), strings.TrimSpace(route.Via)))
  625. }
  626. return strings.Join(items, ",")
  627. }