Initial commit: frpc-client-win v1.0
Single-file Windows frpc service wrapper. Features: - Embeds frpc v0.70.1 (16.7MB) into one 18.9MB EXE - Registers as Windows Service (auto-start, no login required) - Auto-detects frpc config by three-tier search: 1. frpc.ini 2. frpc.toml 3. any *.ini/*.toml in exe dir - Self-healing supervisor: frpc crashes are restarted with exponential backoff (2s..60s), capped to avoid log spam - Console subsystem so install/start show feedback - FreeConsole on SCM start to suppress console window - Logs to <exe-dir>/logs/service.log Subcommands: install start stop remove import -d Smoke test (smoke_linux.go) covers all config detection paths including the custom-name (e.g. frpc-mike.ini) use case.
This commit is contained in:
+124
@@ -0,0 +1,124 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
// 本文件仅用于在 Linux 上 smoke test frpc-client-win 的核心逻辑。
|
||||
// 编译: GOOS=linux GOARCH=amd64 go build -tags linux -o /tmp/frpc-client-test .
|
||||
// 不参与正式 Windows 构建。
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"frpc-client-win/internal/config"
|
||||
"frpc-client-win/internal/logger"
|
||||
)
|
||||
|
||||
//go:embed assets/frpc.exe
|
||||
var smokeFS embed.FS
|
||||
|
||||
func main() {
|
||||
tmpDir, err := os.MkdirTemp("", "frpc-test-*")
|
||||
if err != nil {
|
||||
fmt.Println("mkdir:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
log, err := logger.New(filepath.Join(tmpDir, "logs"))
|
||||
if err != nil {
|
||||
fmt.Println("logger:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer log.Close()
|
||||
|
||||
cfg := config.New(tmpDir, "frpc.exe", smokeFS, log)
|
||||
|
||||
// 测试 EnsureFrpcBinary:应该从 embed 释放 frpc.exe
|
||||
if err := cfg.EnsureFrpcBinary(); err != nil {
|
||||
fmt.Println("EnsureFrpcBinary failed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
stat, err := os.Stat(cfg.FrpcPath)
|
||||
if err != nil {
|
||||
fmt.Println("frpc.exe not released:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✔ frpc.exe released: %s (%d bytes)\n", cfg.FrpcPath, stat.Size())
|
||||
|
||||
// 验证确实是 PE 文件
|
||||
if _, err := fs.Stat(smokeFS, "assets/frpc.exe"); err != nil {
|
||||
fmt.Println("embed missing:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("✔ embed.FS contains assets/frpc.exe")
|
||||
|
||||
// 测试 EnsureIni:应该失败,因为没有配置文件
|
||||
if err := cfg.EnsureIni(); err == nil {
|
||||
fmt.Println("expected EnsureIni to fail, got nil")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Printf("✔ EnsureIni correctly returned: %v\n", err)
|
||||
}
|
||||
|
||||
// 创建一个假的 ini 并测试
|
||||
iniPath := filepath.Join(tmpDir, "frpc.ini")
|
||||
if err := os.WriteFile(iniPath, []byte("[common]\nserver_addr = 127.0.0.1\nserver_port = 7000\n"), 0o644); err != nil {
|
||||
fmt.Println("write ini:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := cfg.EnsureIni(); err != nil {
|
||||
fmt.Println("EnsureIni should now succeed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✔ EnsureIni OK; effective config: %s\n", cfg.EffectiveConfigPath())
|
||||
|
||||
// 测试 EffectiveConfigPath 优先级:新建一个 toml,EffectiveConfigPath 仍然应返回 ini
|
||||
tomlPath := filepath.Join(tmpDir, "frpc.toml")
|
||||
os.WriteFile(tomlPath, []byte("# toml"), 0o644)
|
||||
if cfg.EffectiveConfigPath() != iniPath {
|
||||
fmt.Printf("expected ini preferred, got: %s\n", cfg.EffectiveConfigPath())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Remove(tomlPath)
|
||||
fmt.Println("✔ .ini takes precedence over .toml")
|
||||
|
||||
// === 新场景:任意 *.ini 文件名(frpc-mike.ini 这类) ===
|
||||
// 先清掉标准名 ini,只留一个非标准名的
|
||||
os.Remove(iniPath)
|
||||
mikeIni := filepath.Join(tmpDir, "frpc-mike.ini")
|
||||
if err := os.WriteFile(mikeIni, []byte("[common]\nserver_addr = 10.0.0.1\n"), 0o644); err != nil {
|
||||
fmt.Println("write mike ini:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := cfg.EnsureIni(); err != nil {
|
||||
fmt.Println("EnsureIni should pick frpc-mike.ini via scan:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if cfg.EffectiveConfigPath() != mikeIni {
|
||||
fmt.Printf("expected scan to find frpc-mike.ini, got: %s\n", cfg.EffectiveConfigPath())
|
||||
os.Exit(1)
|
||||
}
|
||||
if cfg.ConfigSource() != "scan" {
|
||||
fmt.Printf("expected source=scan, got: %s\n", cfg.ConfigSource())
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("✔ arbitrary-name ini (frpc-mike.ini) auto-detected via scan")
|
||||
|
||||
// === 多个候选应报错 ===
|
||||
os.WriteFile(filepath.Join(tmpDir, "frpc-other.ini"), []byte("[common]\n"), 0o644)
|
||||
if err := cfg.EnsureIni(); err == nil {
|
||||
fmt.Println("expected ambiguous error, got nil")
|
||||
os.Exit(1)
|
||||
} else if !errors.Is(err, config.ErrIniAmbiguous) {
|
||||
fmt.Printf("expected ErrIniAmbiguous, got: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("✔ multiple candidates correctly reported ambiguous")
|
||||
|
||||
fmt.Println("\n✔ ALL SMOKE TESTS PASSED")
|
||||
}
|
||||
Reference in New Issue
Block a user