diff --git a/bin/frpc_windows_amd64.exe b/bin/frpc_windows_amd64.exe new file mode 100644 index 0000000..4f07c6a Binary files /dev/null and b/bin/frpc_windows_amd64.exe differ diff --git a/frp.go b/frp.go index 813165d..d6da787 100644 --- a/frp.go +++ b/frp.go @@ -2,13 +2,51 @@ 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 + if runtime.GOOS == "windows" { + fileName = "frpc.exe" + } else { + fileName = "frpc" + } + + // 从 embed 读取 + data, err := embeddedFrpc.ReadFile("bin/" + fileName) + if err != nil { + // 如果 embed 中没有对应文件,降级到系统 PATH 查找 + path, err := exec.LookPath(fileName) + if err != nil { + return "", fmt.Errorf("未找到 frpc 可执行文件(embed 和 PATH 均无): %w", err) + } + return path, nil + } + + // 写入临时目录 + tmpPath := os.TempDir() + "/frpc" + if runtime.GOOS == "windows" { + tmpPath += ".exe" + } + err = os.WriteFile(tmpPath, data, 0755) + if err != nil { + return "", fmt.Errorf("写入临时 frpc 文件失败: %w", err) + } + return tmpPath, nil +} + // frpc.toml 模板 - 完全复制你的配置文件结构 const frpcTemplate = `serverAddr = "{{.ServerAddr}}" serverPort = {{.ServerPort}} @@ -37,15 +75,13 @@ localPort = {{.LocalPort}} remotePort = {{.RemotePort}} {{end}}` -// 生成 frpc.toml 文件 +// GenerateFrpcConfig 生成 frpc.toml 配置文件 func GenerateFrpcConfig() error { - // 1. 读取全局配置 cfg, err := GetGlobalConfig() if err != nil { return fmt.Errorf("读取全局配置失败: %w", err) } - // 2. 读取所有启用的隧道 proxies, err := GetProxies() if err != nil { return fmt.Errorf("读取隧道列表失败: %w", err) @@ -58,7 +94,6 @@ func GenerateFrpcConfig() error { } } - // 3. 组合数据 data := struct { *GlobalConfig Proxies []Proxy @@ -67,7 +102,6 @@ func GenerateFrpcConfig() error { Proxies: activeProxies, } - // 4. 渲染模板 tmpl, err := template.New("frpc").Parse(frpcTemplate) if err != nil { return fmt.Errorf("解析模板失败: %w", err) @@ -79,7 +113,6 @@ func GenerateFrpcConfig() error { return fmt.Errorf("渲染模板失败: %w", err) } - // 5. 写入文件 err = os.WriteFile("./frpc.toml", buf.Bytes(), 0644) if err != nil { return fmt.Errorf("写入配置文件失败: %w", err) @@ -88,59 +121,102 @@ func GenerateFrpcConfig() error { return nil } -// 热加载 frpc -func ReloadFrpc() error { - // 先检查 frpc 进程是否在运行 - cmd := exec.Command("pgrep", "-f", "frpc") - output, err := cmd.Output() - if err != nil || len(output) == 0 { - // frpc 没在运行,尝试启动 - return StartFrpc() - } - - // 执行 reload - reloadCmd := exec.Command("frpc", "reload", "-c", "./frpc.toml") - output, err = reloadCmd.CombinedOutput() - if err != nil { - return fmt.Errorf("reload 失败: %s, %w", string(output), err) - } - return nil -} - -// 启动 frpc +// 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) } } - cmd := exec.Command("frpc", "-c", "./frpc.toml") - // 后台运行,不阻塞 - err := cmd.Start() + // 先检查是否已在运行 + 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 } -// StopFrpc 停止 frpc -func StopFrpc() error { - cmd := exec.Command("pkill", "-f", "frpc") - err := cmd.Run() - if err != nil && !strings.Contains(err.Error(), "no process") { - return fmt.Errorf("停止 frpc 失败: %w", err) +// 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 } -// 检查 frpc 状态 -func GetFrpcStatus() (bool, error) { - cmd := exec.Command("pgrep", "-f", "frpc") - output, err := cmd.Output() - if err != nil { - return false, 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 } - return len(output) > 0, nil }