diff --git a/build.exe b/build.exe new file mode 100644 index 0000000..e3ad8f1 Binary files /dev/null and b/build.exe differ diff --git a/frp.go b/frp.go index badeab8..a538805 100644 --- a/frp.go +++ b/frp.go @@ -10,7 +10,6 @@ import ( "runtime" "strings" "sync" - "syscall" "text/template" ) @@ -174,7 +173,7 @@ func StartFrpc() error { cmd := exec.Command(frpcPath, "-c", "./frpc.toml") if runtime.GOOS == "windows" { - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + setWindowHide(cmd) } if err := cmd.Start(); err != nil { return fmt.Errorf("启动 frpc 失败: %w", err) diff --git a/frp_windows.go b/frp_windows.go new file mode 100644 index 0000000..b9baac1 --- /dev/null +++ b/frp_windows.go @@ -0,0 +1,16 @@ +//go:build windows + +package main + +import ( + "os/exec" + "syscall" +) + +// setWindowHide 在 Windows 上隐藏 frpc 命令行窗口 +func setWindowHide(cmd *exec.Cmd) { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.HideWindow = true +} diff --git a/scripts/build.go b/scripts/build.go new file mode 100644 index 0000000..ff354c7 --- /dev/null +++ b/scripts/build.go @@ -0,0 +1,201 @@ +//go:build ignore + +// build.go +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +type Platform struct { + OS string + Arch string + Name string // 显示名称 + Ext string // 文件扩展名(Windows 需要 .exe) +} + +var platforms = []Platform{ + {"windows", "amd64", "Windows x86-64", ".exe"}, + {"windows", "386", "Windows x86", ".exe"}, + {"linux", "amd64", "Linux x86-64", ""}, + {"linux", "arm64", "Linux ARM64", ""}, + {"linux", "armv7", "Linux ARMv7", ""}, +} + +const ( + Version = "1.0.0" + BuildTime = "2026-07-24" + Binary = "frpc-console" +) + +func main() { + // 解析命令行参数 + if len(os.Args) > 1 { + target := os.Args[1] + if target == "all" { + buildAll() + return + } + if target == "clean" { + clean() + return + } + if target == "list" { + listPlatforms() + return + } + // 尝试匹配平台 + for _, p := range platforms { + if target == p.OS+"/"+p.Arch { + build(p, false) + return + } + } + fmt.Println("❌ 不支持的平台:", target) + fmt.Println(" 可用: windows/amd64, windows/386, linux/amd64, linux/arm64, linux/armv7") + fmt.Println(" 或: all, list, clean") + return + } + + // 交互式菜单 + interactiveMenu() +} + +func listPlatforms() { + fmt.Println("可用平台:") + for i, p := range platforms { + fmt.Printf(" %d. %s (%s/%s)\n", i+1, p.Name, p.OS, p.Arch) + } +} + +func interactiveMenu() { + fmt.Println("========================================") + fmt.Println(" frpc-console 多平台构建工具 v" + Version) + fmt.Println("========================================") + fmt.Println() + + for i, p := range platforms { + fmt.Printf(" %d. %s (%s/%s)\n", i+1, p.Name, p.OS, p.Arch) + } + fmt.Println(" a. 全部构建") + fmt.Println(" c. 清理构建产物") + fmt.Println(" q. 退出") + fmt.Println() + + reader := bufio.NewReader(os.Stdin) + fmt.Print("请选择: ") + + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(strings.ToLower(input)) + + switch input { + case "q": + fmt.Println("已退出") + return + case "c": + clean() + return + case "a": + buildAll() + return + default: + // 尝试匹配数字 + var idx int + if n, err := fmt.Sscanf(input, "%d", &idx); n == 1 && err == nil && idx >= 1 && idx <= len(platforms) { + build(platforms[idx-1], false) + return + } + fmt.Println("❌ 无效选择") + interactiveMenu() + } +} + +func buildAll() { + for _, p := range platforms { + build(p, true) + } + fmt.Println("\n✅ 全部构建完成!产物位于 build/ 目录") +} + +func build(p Platform, silent bool) { + if !silent { + fmt.Printf("🔨 构建 %s (%s/%s)...\n", p.Name, p.OS, p.Arch) + } + + outDir := "./build" + if err := os.MkdirAll(outDir, 0755); err != nil { + fmt.Printf("❌ 创建目录失败: %v\n", err) + return + } + + outName := Binary + "-" + p.OS + "-" + p.Arch + p.Ext + outPath := filepath.Join(outDir, outName) + + // 构建命令 + cmd := exec.Command("go.exe", "build", + "-ldflags=-s -w -X main.version="+Version, + "-o", outPath, + ".", + ) + cmd.Env = append(os.Environ(), + "GOOS="+p.OS, + "GOARCH="+p.Arch, + "CGO_ENABLED=0", // 禁用 CGO,保证静态编译 + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + fmt.Printf("❌ 构建失败: %v\n", err) + return + } + + if !silent { + fmt.Printf("✅ 构建完成: %s\n", outPath) + // 显示文件大小 + if info, err := os.Stat(outPath); err == nil { + size := float64(info.Size()) / 1024 / 1024 + fmt.Printf(" 📦 %.2f MB\n", size) + } + } + + // 打包(仅单平台构建时自动打包) + if !silent { + packageBinary(outPath, p) + } +} + +func packageBinary(binPath string, p Platform) { + base := strings.TrimSuffix(filepath.Base(binPath), p.Ext) + dir := filepath.Dir(binPath) + + if p.Ext == ".exe" { + // Windows: zip + zipName := filepath.Join(dir, base+".zip") + cmd := exec.Command("zip", "-j", zipName, binPath) + if err := cmd.Run(); err == nil { + fmt.Printf(" 📦 已打包: %s\n", zipName) + } + } else { + // Linux/macOS: tar.gz + tarName := filepath.Join(dir, base+".tar.gz") + cmd := exec.Command("tar", "-czf", tarName, "-C", dir, filepath.Base(binPath)) + if err := cmd.Run(); err == nil { + fmt.Printf(" 📦 已打包: %s\n", tarName) + } + } +} + +func clean() { + fmt.Println("🧹 清理构建产物...") + if err := os.RemoveAll("./build"); err != nil { + fmt.Printf("❌ 清理失败: %v\n", err) + return + } + fmt.Println("✅ 清理完成") +}