|
@@ -6,6 +6,7 @@ import (
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"io"
|
|
"io"
|
|
|
"net/http"
|
|
"net/http"
|
|
|
|
|
+ "os"
|
|
|
"strings"
|
|
"strings"
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
@@ -18,6 +19,7 @@ import (
|
|
|
configreadersvc "quickip/internal/network/configreader"
|
|
configreadersvc "quickip/internal/network/configreader"
|
|
|
interfacesvc "quickip/internal/network/interfaces"
|
|
interfacesvc "quickip/internal/network/interfaces"
|
|
|
netplansvc "quickip/internal/network/netplan"
|
|
netplansvc "quickip/internal/network/netplan"
|
|
|
|
|
+ "quickip/internal/systemaction"
|
|
|
validatorsvc "quickip/internal/network/validator"
|
|
validatorsvc "quickip/internal/network/validator"
|
|
|
verifysvc "quickip/internal/network/verify"
|
|
verifysvc "quickip/internal/network/verify"
|
|
|
"quickip/internal/tasks"
|
|
"quickip/internal/tasks"
|
|
@@ -34,10 +36,11 @@ type Server struct {
|
|
|
applySvc *applyexecsvc.Service
|
|
applySvc *applyexecsvc.Service
|
|
|
verifySvc *verifysvc.Service
|
|
verifySvc *verifysvc.Service
|
|
|
taskSvc *tasks.Service
|
|
taskSvc *tasks.Service
|
|
|
|
|
+ systemSvc *systemaction.Service
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-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) *Server {
|
|
|
|
|
- return &Server{cfg: cfg, log: log, deviceSvc: deviceSvc, interfaceSvc: interfaceSvc, configSvc: configSvc, validatorSvc: validatorSvc, netplanSvc: netplanSvc, applySvc: applySvc, verifySvc: verifySvc, taskSvc: taskSvc}
|
|
|
|
|
|
|
+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 {
|
|
|
|
|
+ return &Server{cfg: cfg, log: log, deviceSvc: deviceSvc, interfaceSvc: interfaceSvc, configSvc: configSvc, validatorSvc: validatorSvc, netplanSvc: netplanSvc, applySvc: applySvc, verifySvc: verifySvc, taskSvc: taskSvc, systemSvc: systemSvc}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) Run(ctx context.Context) error {
|
|
func (s *Server) Run(ctx context.Context) error {
|
|
@@ -49,6 +52,8 @@ func (s *Server) Run(ctx context.Context) error {
|
|
|
mux.Handle("/api/network/validate", auth.Middleware(s.cfg, http.HandlerFunc(s.handleValidate)))
|
|
mux.Handle("/api/network/validate", auth.Middleware(s.cfg, http.HandlerFunc(s.handleValidate)))
|
|
|
mux.Handle("/api/network/apply", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApply)))
|
|
mux.Handle("/api/network/apply", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApply)))
|
|
|
mux.Handle("/api/network/rollback", auth.Middleware(s.cfg, http.HandlerFunc(s.handleRollback)))
|
|
mux.Handle("/api/network/rollback", auth.Middleware(s.cfg, http.HandlerFunc(s.handleRollback)))
|
|
|
|
|
+ mux.Handle("/api/system/reboot", auth.Middleware(s.cfg, http.HandlerFunc(s.handleReboot)))
|
|
|
|
|
+ mux.Handle("/api/system/shutdown", auth.Middleware(s.cfg, http.HandlerFunc(s.handleShutdown)))
|
|
|
mux.Handle("/api/tasks/", auth.Middleware(s.cfg, http.HandlerFunc(s.handleTaskGet)))
|
|
mux.Handle("/api/tasks/", auth.Middleware(s.cfg, http.HandlerFunc(s.handleTaskGet)))
|
|
|
handler := s.withAccessLog(mux)
|
|
handler := s.withAccessLog(mux)
|
|
|
|
|
|
|
@@ -168,6 +173,10 @@ func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
|
|
|
writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
|
|
writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ if !hasRootPrivileges() {
|
|
|
|
|
+ writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Agent 未以 root 身份运行,无法写入 netplan 或执行 netplan apply。"}}})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
var input model.InterfaceConfig
|
|
var input model.InterfaceConfig
|
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
|
writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
|
|
writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}})
|
|
@@ -197,6 +206,10 @@ func (s *Server) handleRollback(w http.ResponseWriter, r *http.Request) {
|
|
|
writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
|
|
writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ if !hasRootPrivileges() {
|
|
|
|
|
+ writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Agent 未以 root 身份运行,无法恢复 netplan 或执行 netplan apply。"}}})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
var input model.RollbackRequest
|
|
var input model.RollbackRequest
|
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
|
writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": {"请求体格式不正确。"}}})
|
|
writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": {"请求体格式不正确。"}}})
|
|
@@ -233,6 +246,35 @@ func (s *Server) handleTaskGet(w http.ResponseWriter, r *http.Request) {
|
|
|
writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: item})
|
|
writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: item})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (s *Server) handleReboot(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
+ s.handleSystemAction(w, r, "reboot")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (s *Server) handleShutdown(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
+ s.handleSystemAction(w, r, "shutdown")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (s *Server) handleSystemAction(w http.ResponseWriter, r *http.Request, action string) {
|
|
|
|
|
+ if r.Method != http.MethodPost {
|
|
|
|
|
+ writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if !hasRootPrivileges() {
|
|
|
|
|
+ writeJSON(w, http.StatusForbidden, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"Agent 未以 root 身份运行,无法执行重启或关机。"}}})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ task := s.taskSvc.Create()
|
|
|
|
|
+ go s.runSystemTask(task.TaskID, action)
|
|
|
|
|
+ message := "系统任务已提交"
|
|
|
|
|
+ if action == "reboot" {
|
|
|
|
|
+ message = "重启任务已提交"
|
|
|
|
|
+ } else if action == "shutdown" {
|
|
|
|
|
+ message = "关机任务已提交"
|
|
|
|
|
+ }
|
|
|
|
|
+ writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: message, Data: map[string]any{"action": action, "task_id": task.TaskID}})
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (s *Server) runApplyTask(taskID string, input model.InterfaceConfig, managementInterface string) {
|
|
func (s *Server) runApplyTask(taskID string, input model.InterfaceConfig, managementInterface string) {
|
|
|
s.taskSvc.Update(taskID, "running", "validating", "正在校验配置。", false)
|
|
s.taskSvc.Update(taskID, "running", "validating", "正在校验配置。", false)
|
|
|
result := s.validatorSvc.Validate(input)
|
|
result := s.validatorSvc.Validate(input)
|
|
@@ -277,6 +319,39 @@ func (s *Server) runApplyTask(taskID string, input model.InterfaceConfig, manage
|
|
|
s.taskSvc.Update(taskID, "success", "completed", "目标接口配置已成功应用。", false)
|
|
s.taskSvc.Update(taskID, "success", "completed", "目标接口配置已成功应用。", false)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (s *Server) runSystemTask(taskID string, action string) {
|
|
|
|
|
+ detail := "正在发送系统指令。"
|
|
|
|
|
+ if action == "reboot" {
|
|
|
|
|
+ detail = "正在发送重启指令。"
|
|
|
|
|
+ } else if action == "shutdown" {
|
|
|
|
|
+ detail = "正在发送关机指令。"
|
|
|
|
|
+ }
|
|
|
|
|
+ s.taskSvc.Update(taskID, "running", "executing", detail, false)
|
|
|
|
|
+
|
|
|
|
|
+ var err error
|
|
|
|
|
+ switch action {
|
|
|
|
|
+ case "reboot":
|
|
|
|
|
+ err = s.systemSvc.Reboot()
|
|
|
|
|
+ case "shutdown":
|
|
|
|
|
+ err = s.systemSvc.Shutdown()
|
|
|
|
|
+ default:
|
|
|
|
|
+ err = fmt.Errorf("unsupported system action: %s", action)
|
|
|
|
|
+ }
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ s.log.Error("system action failed", "action", action, "error", err.Error())
|
|
|
|
|
+ s.taskSvc.Update(taskID, "failed", "executing", err.Error(), false)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ completedDetail := "系统指令已发送。"
|
|
|
|
|
+ if action == "reboot" {
|
|
|
|
|
+ completedDetail = "系统重启指令已发送。"
|
|
|
|
|
+ } else if action == "shutdown" {
|
|
|
|
|
+ completedDetail = "系统关机指令已发送。"
|
|
|
|
|
+ }
|
|
|
|
|
+ s.taskSvc.Update(taskID, "success", "completed", completedDetail, false)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (s *Server) interfaceExists(name string) bool {
|
|
func (s *Server) interfaceExists(name string) bool {
|
|
|
data, err := s.interfaceSvc.List()
|
|
data, err := s.interfaceSvc.List()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -303,3 +378,7 @@ func writeJSON(w http.ResponseWriter, status int, payload model.APIResponse) {
|
|
|
w.WriteHeader(status)
|
|
w.WriteHeader(status)
|
|
|
_ = json.NewEncoder(w).Encode(payload)
|
|
_ = json.NewEncoder(w).Encode(payload)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+func hasRootPrivileges() bool {
|
|
|
|
|
+ return os.Geteuid() == 0
|
|
|
|
|
+}
|