package frp import ( "embed" "fmt" "os" "os/exec" "path/filepath" "runtime" "sync" ) //go:embed bin/* var embeddedFrpc embed.FS var ( cachedFrpcPath string frpcPathMutex sync.Mutex ) // GetFrpcPath 获取 frpc 二进制路径 // 优先级: 本地缓存 > 程序同目录 > 当前工作目录 > embed 提取到当前目录 > 系统 PATH func GetFrpcPath() (string, error) { frpcPathMutex.Lock() defer frpcPathMutex.Unlock() if cachedFrpcPath != "" { if _, err := os.Stat(cachedFrpcPath); err == nil { return cachedFrpcPath, nil } cachedFrpcPath = "" } 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" case runtime.GOOS == "linux" && runtime.GOARCH == "arm": fileName = "frpc_linux_arm_hf" default: path, err := exec.LookPath("frpc") if err == nil { cachedFrpcPath = path return path, nil } return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH) } // 方法1: 尝试从程序同目录加载 execPath, err := os.Executable() if err == nil { execDir := filepath.Dir(execPath) localPath := filepath.Join(execDir, "frpc") if runtime.GOOS == "windows" { localPath += ".exe" } if _, err := os.Stat(localPath); err == nil { cachedFrpcPath = localPath return localPath, nil } } // 方法2: 尝试从当前工作目录加载 cwdPath := "./frpc" if runtime.GOOS == "windows" { cwdPath += ".exe" } if _, err := os.Stat(cwdPath); err == nil { cachedFrpcPath = cwdPath return cwdPath, nil } // 方法3: 从 embed 提取到当前工作目录 data, err := embeddedFrpc.ReadFile("bin/" + fileName) if err == nil { extractPath := "./frpc" if runtime.GOOS == "windows" { extractPath += ".exe" } if err := os.WriteFile(extractPath, data, 0755); err == nil { cachedFrpcPath = extractPath return extractPath, nil } } // 方法4: 从系统 PATH 查找 path, err := exec.LookPath("frpc") if err == nil { cachedFrpcPath = path return path, nil } return "", fmt.Errorf("未找到 frpc 文件") }