|
|
@@ -299,36 +299,36 @@ func formatLoad(v string) string {
|
|
|
return strconv.FormatFloat(f, 'f', 2, 64)
|
|
|
}
|
|
|
|
|
|
+var services = []struct {
|
|
|
+ name string
|
|
|
+ dir string
|
|
|
+ bin string
|
|
|
+}{
|
|
|
+ {"yfkj-networkd.service", "/opt/yfkj/networkd.service", "networkd"},
|
|
|
+ {"yfkj-timesyncd.service", "/opt/yfkj/timesyncd.service", "timesyncd"},
|
|
|
+ {"yfkj-gnss.service", "/opt/yfkj/gnss.service", "gnss"},
|
|
|
+ {"yfkj-sshd-mqtt-bridge.service", "/opt/yfkj/sshd-mqtt-bridge.service", "sshd-mqtt-bridge"},
|
|
|
+ {"yfkj-camera-capture.service", "/opt/yfkj/camera-capture.service", "camera-capture"},
|
|
|
+ {"yfkj-app-install.service", "/opt/yfkj/app-install.service", "app-install"},
|
|
|
+ {"yfkj-web-ui.service", "/opt/yfkj/web-ui.service", "web-ui"},
|
|
|
+ {"yfkj-upgrade.service", "/opt/yfkj/upgrade.service", "upgrade"},
|
|
|
+}
|
|
|
+
|
|
|
func serviceStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
if r.Method != http.MethodGet {
|
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- services := []struct {
|
|
|
- name string
|
|
|
- dir string
|
|
|
- }{
|
|
|
- {"yfkj-networkd.service", "/opt/yfkj/networkd.service"},
|
|
|
- {"yfkj-timesyncd.service", "/opt/yfkj/timesyncd.service"},
|
|
|
- {"yfkj-gnss.service", "/opt/yfkj/gnss.service"},
|
|
|
- {"yfkj-sshd-mqtt-bridge.service", "/opt/yfkj/sshd-mqtt-bridge.service"},
|
|
|
- {"yfkj-camera-capture.service", "/opt/yfkj/camera-capture.service"},
|
|
|
- {"yfkj-app-install.service", "/opt/yfkj/app-install.service"},
|
|
|
- {"yfkj-web-ui.service", "/opt/yfkj/web-ui.service"},
|
|
|
- {"yfkj-upgrade.service", "/opt/yfkj/upgrade.service"},
|
|
|
- }
|
|
|
-
|
|
|
data := make(map[string]string, len(services)*2)
|
|
|
|
|
|
for i, s := range services {
|
|
|
n := i + 1
|
|
|
|
|
|
status := noValue
|
|
|
- if out, err := exec.Command("systemctl", "is-active", s.name).Output(); err == nil {
|
|
|
- if strings.TrimSpace(string(out)) == "active" {
|
|
|
- status = "🟢运行中..."
|
|
|
- }
|
|
|
+ if out, err := exec.Command("systemctl", "is-active", s.name).Output(); err == nil &&
|
|
|
+ strings.TrimSpace(string(out)) == "active" {
|
|
|
+ status = "🟢运行中..."
|
|
|
}
|
|
|
|
|
|
data[fmt.Sprintf("var%d", n)] = status
|
|
|
@@ -343,3 +343,145 @@ func serviceStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
json.NewEncoder(w).Encode(data)
|
|
|
}
|
|
|
+
|
|
|
+type VersionSwitchReq struct {
|
|
|
+ Service string `json:"service"`
|
|
|
+ Action string `json:"action"`
|
|
|
+ Password string `json:"password"`
|
|
|
+}
|
|
|
+
|
|
|
+type VersionResp struct {
|
|
|
+ Status string `json:"status"`
|
|
|
+ Message string `json:"message,omitempty"`
|
|
|
+}
|
|
|
+
|
|
|
+var versionSwitchLock sync.Mutex
|
|
|
+
|
|
|
+func serviceVersionSwitchHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if r.Method != http.MethodPost {
|
|
|
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ versionSwitchLock.Lock()
|
|
|
+ defer versionSwitchLock.Unlock()
|
|
|
+
|
|
|
+ var req VersionSwitchReq
|
|
|
+ if json.NewDecoder(r.Body).Decode(&req) != nil {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: "invalid request"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if req.Password != "yfkj123456" {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: "输入的密码不正确"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ var cfg struct{ name, dir, bin string }
|
|
|
+ for _, v := range services {
|
|
|
+ if v.name == req.Service {
|
|
|
+ cfg = v
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if cfg.name == "" {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: "unknown service"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ link := filepath.Join(cfg.dir, cfg.bin)
|
|
|
+
|
|
|
+ oldLink, err := os.Readlink(link)
|
|
|
+ if err != nil {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: "读取当前版本失败"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ current := filepath.Base(filepath.Dir(oldLink))
|
|
|
+ if current != "a" && current != "b" && current != "c" {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: "当前版本状态异常"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+ target := ""
|
|
|
+
|
|
|
+ switch req.Action {
|
|
|
+ case "factory":
|
|
|
+ if current == "a" {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "ok", Message: "当前已是出厂版本"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+ target = "a"
|
|
|
+ case "previous":
|
|
|
+ switch current {
|
|
|
+ case "b":
|
|
|
+ if info, err := os.Stat(filepath.Join(cfg.dir, "c", cfg.bin)); err == nil && !info.IsDir() && info.Size() > 0 && info.Mode().Perm()&0111 != 0 {
|
|
|
+ target = "c"
|
|
|
+ }
|
|
|
+ case "c":
|
|
|
+ if info, err := os.Stat(filepath.Join(cfg.dir, "b", cfg.bin)); err == nil && !info.IsDir() && info.Size() > 0 && info.Mode().Perm()&0111 != 0 {
|
|
|
+ target = "b"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if target == "" {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{
|
|
|
+ Status: "error",
|
|
|
+ Message: "当前没有可回退的上一版本",
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: "invalid action"})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ targetBin := filepath.Join(cfg.dir, target, cfg.bin)
|
|
|
+
|
|
|
+ info, err := os.Stat(targetBin)
|
|
|
+ if err != nil || info.IsDir() || info.Size() == 0 || info.Mode().Perm()&0111 == 0 {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: fmt.Sprintf("目标版本文件异常: %s", target)})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if out, err := exec.Command("systemctl", "stop", req.Service).CombinedOutput(); err != nil {
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: fmt.Sprintf("停止当前服务失败: %s", out)})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ tmp := filepath.Join(cfg.dir, cfg.bin+".tmp")
|
|
|
+ os.Remove(tmp)
|
|
|
+
|
|
|
+ if err := os.Symlink(filepath.Join(target, cfg.bin), tmp); err != nil {
|
|
|
+ exec.Command("systemctl", "start", req.Service).Run()
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: err.Error()})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := os.Rename(tmp, link); err != nil {
|
|
|
+ os.Remove(tmp)
|
|
|
+ exec.Command("systemctl", "start", req.Service).Run()
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "error", Message: err.Error()})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if out, err := exec.Command("systemctl", "start", req.Service).CombinedOutput(); err != nil {
|
|
|
+ tmp := filepath.Join(cfg.dir, cfg.bin+".tmp")
|
|
|
+ os.Remove(tmp)
|
|
|
+
|
|
|
+ if e := os.Symlink(oldLink, tmp); e == nil {
|
|
|
+ if e = os.Rename(tmp, link); e != nil {
|
|
|
+ os.Remove(tmp)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ exec.Command("systemctl", "start", req.Service).Run()
|
|
|
+
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{
|
|
|
+ Status: "error",
|
|
|
+ Message: fmt.Sprintf("目标版本启动失败,已恢复旧版本: %s", out),
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ json.NewEncoder(w).Encode(VersionResp{Status: "ok"})
|
|
|
+}
|