server.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. package httpserver
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "time"
  11. "quickip/internal/auth"
  12. "quickip/internal/config"
  13. "quickip/internal/deviceinfo"
  14. "quickip/internal/logger"
  15. "quickip/internal/model"
  16. applyexecsvc "quickip/internal/network/applyexec"
  17. configreadersvc "quickip/internal/network/configreader"
  18. interfacesvc "quickip/internal/network/interfaces"
  19. netplansvc "quickip/internal/network/netplan"
  20. "quickip/internal/systemaction"
  21. validatorsvc "quickip/internal/network/validator"
  22. verifysvc "quickip/internal/network/verify"
  23. "quickip/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. verifySvc *verifysvc.Service
  35. taskSvc *tasks.Service
  36. systemSvc *systemaction.Service
  37. }
  38. 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, verifySvc *verifysvc.Service, taskSvc *tasks.Service, systemSvc *systemaction.Service) *Server {
  39. return &Server{cfg: cfg, log: log, deviceSvc: deviceSvc, interfaceSvc: interfaceSvc, configSvc: configSvc, validatorSvc: validatorSvc, netplanSvc: netplanSvc, applySvc: applySvc, verifySvc: verifySvc, taskSvc: taskSvc, systemSvc: systemSvc}
  40. }
  41. func (s *Server) Run(ctx context.Context) error {
  42. mux := http.NewServeMux()
  43. mux.Handle("/api/health", auth.Middleware(s.cfg, http.HandlerFunc(s.handleHealth)))
  44. mux.Handle("/api/device/info", auth.Middleware(s.cfg, http.HandlerFunc(s.handleDeviceInfo)))
  45. mux.Handle("/api/network/interfaces", auth.Middleware(s.cfg, http.HandlerFunc(s.handleInterfaces)))
  46. mux.Handle("/api/network/config", auth.Middleware(s.cfg, http.HandlerFunc(s.handleConfig)))
  47. mux.Handle("/api/network/validate", auth.Middleware(s.cfg, http.HandlerFunc(s.handleValidate)))
  48. mux.Handle("/api/network/apply", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApply)))
  49. mux.Handle("/api/network/rollback", auth.Middleware(s.cfg, http.HandlerFunc(s.handleRollback)))
  50. mux.Handle("/api/system/reboot", auth.Middleware(s.cfg, http.HandlerFunc(s.handleReboot)))
  51. mux.Handle("/api/system/shutdown", auth.Middleware(s.cfg, http.HandlerFunc(s.handleShutdown)))
  52. mux.Handle("/api/tasks/", auth.Middleware(s.cfg, http.HandlerFunc(s.handleTaskGet)))
  53. handler := s.withAccessLog(mux)
  54. server := &http.Server{
  55. Addr: fmt.Sprintf("%s:%d", s.cfg.HTTPHost, s.cfg.HTTPPort),
  56. Handler: handler,
  57. ReadHeaderTimeout: 5 * time.Second,
  58. }
  59. go func() {
  60. <-ctx.Done()
  61. shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  62. defer cancel()
  63. _ = server.Shutdown(shutdownCtx)
  64. }()
  65. s.log.Info("http server listening", "addr", server.Addr)
  66. err := server.ListenAndServe()
  67. if err == http.ErrServerClosed {
  68. return nil
  69. }
  70. return err
  71. }
  72. func (s *Server) withAccessLog(next http.Handler) http.Handler {
  73. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  74. started := time.Now()
  75. rw := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK}
  76. next.ServeHTTP(rw, r)
  77. s.log.Info(
  78. "http request completed",
  79. "method", r.Method,
  80. "path", r.URL.Path,
  81. "query", r.URL.RawQuery,
  82. "remote", r.RemoteAddr,
  83. "status", rw.statusCode,
  84. "duration_ms", time.Since(started).Milliseconds(),
  85. )
  86. })
  87. }
  88. type statusRecorder struct {
  89. http.ResponseWriter
  90. statusCode int
  91. }
  92. func (r *statusRecorder) WriteHeader(statusCode int) {
  93. r.statusCode = statusCode
  94. r.ResponseWriter.WriteHeader(statusCode)
  95. }
  96. func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) {
  97. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: map[string]any{"status": "运行中", "agent_version": s.cfg.AgentVersion}})
  98. }
  99. func (s *Server) handleDeviceInfo(w http.ResponseWriter, _ *http.Request) {
  100. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: s.deviceSvc.Get()})
  101. }
  102. func (s *Server) handleInterfaces(w http.ResponseWriter, _ *http.Request) {
  103. data, err := s.interfaceSvc.List()
  104. if err != nil {
  105. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}})
  106. return
  107. }
  108. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: data})
  109. }
  110. func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
  111. interfaceName := r.URL.Query().Get("interface")
  112. if interfaceName == "" {
  113. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 interface 参数。"}}})
  114. return
  115. }
  116. if !s.interfaceExists(interfaceName) {
  117. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"目标接口不存在。"}}})
  118. return
  119. }
  120. data, err := s.configSvc.Read(interfaceName)
  121. if err != nil {
  122. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}})
  123. return
  124. }
  125. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: data})
  126. }
  127. func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) {
  128. if r.Method != http.MethodPost {
  129. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  130. return
  131. }
  132. body, err := io.ReadAll(r.Body)
  133. if err != nil {
  134. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体读取失败。"}}})
  135. return
  136. }
  137. var input model.InterfaceConfig
  138. if err := json.Unmarshal(body, &input); err != nil {
  139. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  140. return
  141. }
  142. if input.Interface != "" && !s.interfaceExists(input.Interface) {
  143. result := model.ValidateResponse{Valid: false, Errors: []string{"目标接口不存在。"}, Warnings: []string{}}
  144. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  145. return
  146. }
  147. result := s.validatorSvc.Validate(input)
  148. if !result.Valid {
  149. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  150. return
  151. }
  152. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "校验通过", Data: result})
  153. }
  154. func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
  155. if r.Method != http.MethodPost {
  156. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  157. return
  158. }
  159. if !hasRootPrivileges() {
  160. writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Agent 未以 root 身份运行,无法写入 netplan 或执行 netplan apply。"}}})
  161. return
  162. }
  163. var input model.InterfaceConfig
  164. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  165. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
  166. return
  167. }
  168. if !s.interfaceExists(input.Interface) {
  169. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: model.ValidateResponse{Valid: false, Errors: []string{"目标接口不存在。"}}})
  170. return
  171. }
  172. result := s.validatorSvc.Validate(input)
  173. if !result.Valid {
  174. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result})
  175. return
  176. }
  177. management := s.currentManagementInterface()
  178. if management == "" {
  179. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"未能识别管理接口。"}}})
  180. return
  181. }
  182. task := s.taskSvc.Create()
  183. go s.runApplyTask(task.TaskID, input, management)
  184. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "配置任务已提交", Data: map[string]any{"interface": input.Interface, "task_id": task.TaskID}})
  185. }
  186. func (s *Server) handleRollback(w http.ResponseWriter, r *http.Request) {
  187. if r.Method != http.MethodPost {
  188. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  189. return
  190. }
  191. if !hasRootPrivileges() {
  192. writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Agent 未以 root 身份运行,无法恢复 netplan 或执行 netplan apply。"}}})
  193. return
  194. }
  195. var input model.RollbackRequest
  196. if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
  197. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": {"请求体格式不正确。"}}})
  198. return
  199. }
  200. filePath, err := s.netplanSvc.FindSingleFile()
  201. if err != nil {
  202. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}})
  203. return
  204. }
  205. backupPath := filePath + ".quickip.bak"
  206. if err := s.netplanSvc.Restore(filePath, backupPath); err != nil {
  207. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 3004, Message: "回滚失败", Data: map[string]string{"error": err.Error()}})
  208. return
  209. }
  210. if err := s.applySvc.Apply(); err != nil {
  211. writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 3004, Message: "回滚失败", Data: map[string]string{"error": err.Error()}})
  212. return
  213. }
  214. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "回滚成功", Data: map[string]any{"interface": input.Interface, "rolled_back": true}})
  215. }
  216. func (s *Server) handleTaskGet(w http.ResponseWriter, r *http.Request) {
  217. taskID := strings.TrimPrefix(r.URL.Path, "/api/tasks/")
  218. if taskID == "" {
  219. writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 task_id。"}}})
  220. return
  221. }
  222. item, ok := s.taskSvc.Get(taskID)
  223. if !ok {
  224. writeJSON(w, http.StatusNotFound, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  225. return
  226. }
  227. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: item})
  228. }
  229. func (s *Server) handleReboot(w http.ResponseWriter, r *http.Request) {
  230. s.handleSystemAction(w, r, "reboot")
  231. }
  232. func (s *Server) handleShutdown(w http.ResponseWriter, r *http.Request) {
  233. s.handleSystemAction(w, r, "shutdown")
  234. }
  235. func (s *Server) handleSystemAction(w http.ResponseWriter, r *http.Request, action string) {
  236. if r.Method != http.MethodPost {
  237. writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
  238. return
  239. }
  240. if !hasRootPrivileges() {
  241. writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Agent 未以 root 身份运行,无法执行重启或关机。"}}})
  242. return
  243. }
  244. task := s.taskSvc.Create()
  245. go s.runSystemTask(task.TaskID, action)
  246. message := "系统任务已提交"
  247. if action == "reboot" {
  248. message = "重启任务已提交"
  249. } else if action == "shutdown" {
  250. message = "关机任务已提交"
  251. }
  252. writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: message, Data: map[string]any{"action": action, "task_id": task.TaskID}})
  253. }
  254. func (s *Server) runApplyTask(taskID string, input model.InterfaceConfig, managementInterface string) {
  255. s.taskSvc.Update(taskID, "running", "validating", "正在校验配置。", false)
  256. result := s.validatorSvc.Validate(input)
  257. if !result.Valid {
  258. s.taskSvc.Update(taskID, "failed", "validating", "配置校验失败。", false)
  259. return
  260. }
  261. filePath, err := s.netplanSvc.FindSingleFile()
  262. if err != nil {
  263. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  264. return
  265. }
  266. s.taskSvc.Update(taskID, "running", "writing_netplan", "正在写入 netplan 配置。", false)
  267. backupPath, err := s.netplanSvc.Backup(filePath)
  268. if err != nil {
  269. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  270. return
  271. }
  272. if err := s.netplanSvc.Write(filePath, input.Interface, input, managementInterface, s.cfg.MaintenanceCIDR); err != nil {
  273. s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false)
  274. return
  275. }
  276. s.taskSvc.Update(taskID, "running", "applying", "正在应用 netplan 配置。", false)
  277. if err := s.applySvc.Apply(); err != nil {
  278. _ = s.netplanSvc.Restore(filePath, backupPath)
  279. _ = s.applySvc.Apply()
  280. s.taskSvc.Update(taskID, "rolled_back", "rolling_back", "配置失败,已自动回滚。", true)
  281. return
  282. }
  283. s.taskSvc.Update(taskID, "running", "verifying", "正在验证配置结果。", false)
  284. if err := s.verifySvc.Verify(input); err != nil {
  285. _ = s.netplanSvc.Restore(filePath, backupPath)
  286. _ = s.applySvc.Apply()
  287. s.taskSvc.Update(taskID, "rolled_back", "rolling_back", "配置失败,已自动回滚。", true)
  288. return
  289. }
  290. s.taskSvc.Update(taskID, "success", "completed", "目标接口配置已成功应用。", false)
  291. }
  292. func (s *Server) runSystemTask(taskID string, action string) {
  293. detail := "正在发送系统指令。"
  294. if action == "reboot" {
  295. detail = "正在发送重启指令。"
  296. } else if action == "shutdown" {
  297. detail = "正在发送关机指令。"
  298. }
  299. s.taskSvc.Update(taskID, "running", "executing", detail, false)
  300. var err error
  301. switch action {
  302. case "reboot":
  303. err = s.systemSvc.Reboot()
  304. case "shutdown":
  305. err = s.systemSvc.Shutdown()
  306. default:
  307. err = fmt.Errorf("unsupported system action: %s", action)
  308. }
  309. if err != nil {
  310. s.log.Error("system action failed", "action", action, "error", err.Error())
  311. s.taskSvc.Update(taskID, "failed", "executing", err.Error(), false)
  312. return
  313. }
  314. completedDetail := "系统指令已发送。"
  315. if action == "reboot" {
  316. completedDetail = "系统重启指令已发送。"
  317. } else if action == "shutdown" {
  318. completedDetail = "系统关机指令已发送。"
  319. }
  320. s.taskSvc.Update(taskID, "success", "completed", completedDetail, false)
  321. }
  322. func (s *Server) interfaceExists(name string) bool {
  323. data, err := s.interfaceSvc.List()
  324. if err != nil {
  325. return false
  326. }
  327. for _, item := range data.Interfaces {
  328. if item.SystemName == name {
  329. return true
  330. }
  331. }
  332. return false
  333. }
  334. func (s *Server) currentManagementInterface() string {
  335. data, err := s.interfaceSvc.List()
  336. if err != nil {
  337. return ""
  338. }
  339. return data.ManagementInterface
  340. }
  341. func writeJSON(w http.ResponseWriter, status int, payload model.APIResponse) {
  342. w.Header().Set("Content-Type", "application/json")
  343. w.WriteHeader(status)
  344. _ = json.NewEncoder(w).Encode(payload)
  345. }
  346. func hasRootPrivileges() bool {
  347. return os.Geteuid() == 0
  348. }