package main import ( "bytes" "embed" "fmt" "os" "os/exec" "runtime" "strings" "syscall" "text/template" ) //go:embed bin/* var embeddedFrpc embed.FS // 获取当前平台对应的 frpc 路径(从 embed 解压到临时目录) func getFrpcPath() (string, error) { var fileName string switch { case runtime.GOOS == "windows" && runtime.GOARCH == "amd64": fileName = "frpc_windows_amd64.exe" case runtime.GOOS == "linux" && runtime.GOARCH == "amd64": fileName = "frpc_linux_amd64" case runtime.GOOS == "linux" && runtime.GOARCH == "arm64": fileName = "frpc_linux_arm64" default: // 降级:尝试从 PATH 中查找 frpc path, err := exec.LookPath("frpc") if err == nil { return path, nil } return "", fmt.Errorf("不支持的平台: %s/%s,且 PATH 中未找到 frpc", runtime.GOOS, runtime.GOARCH) } // 从 embed 读取对应文件 data, err := embeddedFrpc.ReadFile("bin/" + fileName) if err != nil { // 如果 embed 中没有,降级到 PATH path, lookErr := exec.LookPath("frpc") if lookErr == nil { return path, nil } return "", fmt.Errorf("未找到 frpc 文件 (embed 和 PATH 均无): %w", err) } // 写入临时目录 tmpPath := os.TempDir() + "/frpc" if runtime.GOOS == "windows" { tmpPath += ".exe" } if err := os.WriteFile(tmpPath, data, 0755); err != nil { return "", fmt.Errorf("写入临时文件失败: %w", err) } return tmpPath, nil } // frpc.toml 模板 - 完全复制你的配置文件结构 const frpcTemplate = `serverAddr = "{{.ServerAddr}}" serverPort = {{.ServerPort}} [auth] token = "{{.Token}}" [log] to = "./frpc.log" level = "{{.LogLevel}}" maxDays = {{.LogMaxDays}} [transport] tcpMux = {{.TcpMux}} tcpMuxKeepaliveInterval = {{.TcpMuxKeepalive}} heartbeatInterval = {{.HeartbeatInterval}} heartbeatTimeout = {{.HeartbeatTimeout}} poolCount = {{.PoolCount}} {{range .Proxies}} [[proxies]] name = "{{.Name}}" type = "{{.Type}}" localIP = "{{.LocalIP}}" localPort = {{.LocalPort}} remotePort = {{.RemotePort}} {{end}}` // GenerateFrpcConfig 生成 frpc.toml 配置文件 func GenerateFrpcConfig() error { cfg, err := GetGlobalConfig() if err != nil { return fmt.Errorf("读取全局配置失败: %w", err) } proxies, err := GetProxies() if err != nil { return fmt.Errorf("读取隧道列表失败: %w", err) } var activeProxies []Proxy for _, p := range proxies { if p.Enabled { activeProxies = append(activeProxies, p) } } data := struct { *GlobalConfig Proxies []Proxy }{ GlobalConfig: cfg, Proxies: activeProxies, } tmpl, err := template.New("frpc").Parse(frpcTemplate) if err != nil { return fmt.Errorf("解析模板失败: %w", err) } var buf bytes.Buffer err = tmpl.Execute(&buf, data) if err != nil { return fmt.Errorf("渲染模板失败: %w", err) } err = os.WriteFile("./frpc.toml", buf.Bytes(), 0644) if err != nil { return fmt.Errorf("写入配置文件失败: %w", err) } return nil } // StartFrpc 启动 frpc(后台运行) func StartFrpc() error { frpcPath, err := getFrpcPath() if err != nil { return fmt.Errorf("获取 frpc 路径失败: %w", err) } // 检查配置文件是否存在 if _, err := os.Stat("./frpc.toml"); os.IsNotExist(err) { if err := GenerateFrpcConfig(); err != nil { return fmt.Errorf("生成配置文件失败: %w", err) } } // 先检查是否已在运行 running, _ := GetFrpcStatus() if running { return nil // 已经在运行,不重复启动 } cmd := exec.Command(frpcPath, "-c", "./frpc.toml") // Windows 下需要隐藏窗口(可选) if runtime.GOOS == "windows" { cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} } err = cmd.Start() if err != nil { return fmt.Errorf("启动 frpc 失败: %w", err) } return nil } // ReloadFrpc 热加载 frpc 配置 func ReloadFrpc() error { frpcPath, err := getFrpcPath() if err != nil { return fmt.Errorf("获取 frpc 路径失败: %w", err) } // 检查 frpc 是否在运行 running, err := GetFrpcStatus() if err != nil { return fmt.Errorf("检查 frpc 状态失败: %w", err) } if !running { // 没运行就直接启动 return StartFrpc() } // 执行 reload cmd := exec.Command(frpcPath, "reload", "-c", "./frpc.toml") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("reload 失败: %s, %w", string(output), err) } return nil } // StopFrpc 停止 frpc func StopFrpc() error { if runtime.GOOS == "windows" { // Windows 用 taskkill cmd := exec.Command("taskkill", "/F", "/IM", "frpc.exe") err := cmd.Run() if err != nil && !strings.Contains(err.Error(), "not found") { return fmt.Errorf("停止 frpc 失败: %w", err) } return nil } else { // Linux/Unix 用 pkill cmd := exec.Command("pkill", "-f", "frpc") err := cmd.Run() if err != nil && !strings.Contains(err.Error(), "no process") { return fmt.Errorf("停止 frpc 失败: %w", err) } return nil } } // GetFrpcStatus 检查 frpc 是否在运行 func GetFrpcStatus() (bool, error) { if runtime.GOOS == "windows" { // Windows 用 tasklist 检查 cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq frpc.exe") output, err := cmd.CombinedOutput() if err != nil { return false, nil } return strings.Contains(string(output), "frpc.exe"), nil } else { // Linux/Unix 用 pgrep cmd := exec.Command("pgrep", "-f", "frpc") output, err := cmd.Output() if err != nil { return false, nil } return len(output) > 0, nil } }