//go:build windows // +build windows // Package main 是 frpc-client-win 的入口。 // // 双模式入口: // // 带子命令 -> 服务管理 (install/remove/start/stop/run/import) // 不带子命令 -> 服务模式运行(已注册为服务时) // 不带子命令 -d -> 控制台前台运行(调试用) // // frpc.exe 通过 embed.FS 内嵌到 binary 中,部署只需一个 EXE。 // // 编译说明: // // 默认用 CONSOLE 子系统,这样 install/start/import 等 CLI 子命令能输出反馈。 // 服务运行时 (svc.Run) 即使从 console EXE 启动,SCM 也会自动脱离控制台窗口, // 不会出现黑窗。 // 不要加 -H windowsgui,否则 CLI 子命令的 stdout 会被静默吃掉(踩过的坑)。 package main import ( "embed" "fmt" "os" "path/filepath" "frpc-client-win/internal/config" "frpc-client-win/internal/logger" "frpc-client-win/internal/service" ) //go:embed assets/frpc.exe var frpcFS embed.FS // frpcBinName 是嵌入的 frpc 二进制文件名(同目录会优先使用本地版)。 const frpcBinName = "frpc.exe" func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } } func run() error { exeDir, err := exeDir() if err != nil { return err } // 注意:日志路径必须是绝对的! // SCM 启动的服务默认工作目录是 C:\Windows\System32, // 用相对路径会把日志写到 System32 下,而不是 EXE 同目录。 logDir := filepath.Join(exeDir, "logs") log, err := logger.New(logDir) if err != nil { return fmt.Errorf("init logger at %s: %w", logDir, err) } defer log.Close() cfg := config.New(exeDir, frpcBinName, frpcFS, log) // 没有参数 -> 直接以服务/前台模式启动 if len(os.Args) < 2 { return service.Run(cfg) } switch os.Args[1] { case "install": return service.Install() case "remove", "uninstall": return service.Remove() case "start": return service.Start() case "stop": return service.Stop() case "run": // 'run' 子命令强制以服务模式运行(SCM 调用此子命令) return service.Run(cfg) case "import": return config.ImportInteractive(cfg) case "-d", "console": // 控制台前台模式,直接启动 frpc 看日志 return service.RunForeground(cfg) default: return fmt.Errorf("unknown command: %s\n\nUsage:\n install | remove | start | stop | import | -d", os.Args[1]) } } // exeDir 返回当前可执行文件所在目录。 // // 当作为 Windows 服务运行时,os.Getwd() 指向 System32, // 所以必须用 Module 路径而不是当前工作目录。 func exeDir() (string, error) { exe, err := os.Executable() if err != nil { return "", fmt.Errorf("locate executable: %w", err) } return filepath.Dir(exe), nil }