Files
frpc-client-win/smoke_linux.go
T
cnbugs 69801ecc3f 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.
2026-08-10 22:11:43 +08:00

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")
}