server.go 29 KB

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