69801ecc3f
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.
124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
//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")
|
|
} |