package frp import ( "context" "log" "os" "os/exec" "runtime" "strconv" "strings" "syscall" "frpc-console/internal/process" ) // ================================================================ // 兼容层:保持对外接口不变 // 这些函数供 api 调用,实际委托给 process.Manager // ================================================================ // IsRunning 检查 frpc 是否在运行 func IsRunning() bool { pm := process.GetGlobalManager() if pm != nil { status, err := pm.Status() if err != nil { log.Printf("[WARN] ProcessManager.Status() 失败: %v,降级到 PID 文件", err) return isRunningLegacy() } return status.Phase == "RUNNING" } return isRunningLegacy() } // Start 启动 frpc (幂等) func Start() error { pm := process.GetGlobalManager() if pm != nil { log.Println("[INFO] 使用 ProcessManager 启动 frpc") return pm.Start(context.Background()) } log.Println("[WARN] ProcessManager 未初始化,使用兼容模式启动 frpc") return startLegacy() } // Stop 停止 frpc (幂等) func Stop() error { pm := process.GetGlobalManager() if pm != nil { log.Println("[INFO] 使用 ProcessManager 停止 frpc") return pm.Stop(context.Background()) } log.Println("[WARN] ProcessManager 未初始化,使用兼容模式停止 frpc") return stopLegacy() } // Restart 重启 frpc (原子操作) func Restart() error { pm := process.GetGlobalManager() if pm != nil { log.Println("[INFO] 使用 ProcessManager 重启 frpc") return pm.Restart(context.Background()) } log.Println("[WARN] ProcessManager 未初始化,使用兼容模式重启 frpc") if err := stopLegacy(); err != nil { return err } return startLegacy() } // GetStatus 获取 frpc 详细状态 (供 API 调用) func GetStatus() (map[string]interface{}, error) { pm := process.GetGlobalManager() if pm != nil { status, err := pm.Status() if err != nil { return nil, err } return map[string]interface{}{ "state": status.Phase, "pid": status.PID, "port": status.Port, }, nil } running := isRunningLegacy() return map[string]interface{}{ "state": map[bool]string{true: "running", false: "stopped"}[running], "pid": 0, "port": 0, "legacy": true, }, nil } // Reload 热加载 frpc 配置 func Reload() error { pm := process.GetGlobalManager() if pm == nil { return reloadLegacy() } status, err := pm.Status() if err != nil { return err } if status.Phase != "RUNNING" { return pm.Start(context.Background()) } frpcPath, err := GetFrpcPath() if err != nil { return err } cmd := exec.Command(frpcPath, "reload", "-c", "./data/frpc.toml") output, err := cmd.CombinedOutput() if err != nil { log.Printf("⚠️ 热加载失败 (%v),降级为重启 frpc", err) log.Printf(" reload 输出: %s", string(output)) if err := pm.Restart(context.Background()); err != nil { return err } return nil } log.Printf("✅ frpc 热加载成功: %s", string(output)) return nil } // ================================================================ // 旧版实现 (降级方案) // ================================================================ func isRunningLegacy() bool { pidData, err := os.ReadFile("./data/frpc.pid") if err != nil { return false } pid, err := strconv.Atoi(strings.TrimSpace(string(pidData))) if err != nil { return false } if runtime.GOOS == "windows" { cmd := exec.Command("tasklist", "/FI", "PID eq", strconv.Itoa(pid)) output, err := cmd.CombinedOutput() if err != nil { return false } return strings.Contains(string(output), strconv.Itoa(pid)) } proc, err := os.FindProcess(pid) if err != nil { return false } return proc.Signal(syscall.Signal(0)) == nil } func startLegacy() error { frpcPath, err := GetFrpcPath() if err != nil { return err } if err := os.MkdirAll("./data", 0755); err != nil { return err } if _, err := os.Stat("./data/frpc.toml"); os.IsNotExist(err) { if err := GenerateConfig(); err != nil { return err } } if isRunningLegacy() { return nil } os.Remove("./data/frpc.pid") cmd := exec.Command(frpcPath, "-c", "./data/frpc.toml") setWindowHide(cmd) setSysProcAttr(cmd) logFile, err := os.OpenFile("./data/frpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return err } cmd.Stdout = logFile cmd.Stderr = logFile if err := cmd.Start(); err != nil { return err } go func() { if err := cmd.Wait(); err != nil { log.Printf("frpc 子进程退出: %v", err) } os.Remove("./data/frpc.pid") }() return os.WriteFile("./data/frpc.pid", []byte(strconv.Itoa(cmd.Process.Pid)), 0644) } func stopLegacy() error { if runtime.GOOS == "windows" { cmd := exec.Command("taskkill", "/F", "/IM", "frpc.exe") if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") { return err } os.Remove("./data/frpc.pid") return nil } pidData, err := os.ReadFile("./data/frpc.pid") if err != nil { cmd := exec.Command("pkill", "-f", "frpc") if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") { return err } return nil } pid, _ := strconv.Atoi(strings.TrimSpace(string(pidData))) proc, err := os.FindProcess(pid) if err != nil { os.Remove("./data/frpc.pid") return nil } if err := proc.Kill(); err != nil { return err } os.Remove("./data/frpc.pid") return nil } func reloadLegacy() error { if !isRunningLegacy() { return startLegacy() } frpcPath, err := GetFrpcPath() if err != nil { return err } cmd := exec.Command(frpcPath, "reload", "-c", "./data/frpc.toml") output, err := cmd.CombinedOutput() if err != nil { log.Printf("⚠️ 热加载失败 (%v),降级为重启 frpc", err) log.Printf(" reload 输出: %s", string(output)) if stopErr := stopLegacy(); stopErr != nil { return stopErr } return startLegacy() } log.Printf("✅ frpc 热加载成功 (兼容模式): %s", string(output)) return nil }