首次推送

This commit is contained in:
2026-07-27 00:00:27 +08:00
commit d6ef302b8c
28 changed files with 4760 additions and 0 deletions
+267
View File
@@ -0,0 +1,267 @@
package main
import (
"bytes"
"embed"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
"text/template"
)
//go:embed bin/*
var embeddedFrps embed.FS
//go:embed frps.tmpl
var FrpsTemplateContent string
var (
cachedFrpsPath string
frpsPathMutex sync.Mutex
)
// ============================================================
// 获取 frps 路径
// ============================================================
func getFrpsPath() (string, error) {
frpsPathMutex.Lock()
defer frpsPathMutex.Unlock()
if cachedFrpsPath != "" {
if _, err := os.Stat(cachedFrpsPath); err == nil {
return cachedFrpsPath, nil
}
cachedFrpsPath = ""
}
var fileName string
switch {
case runtime.GOOS == "windows" && runtime.GOARCH == "amd64":
fileName = "frps_windows_amd64.exe"
case runtime.GOOS == "linux" && runtime.GOARCH == "amd64":
fileName = "frps_linux_amd64"
case runtime.GOOS == "linux" && runtime.GOARCH == "arm64":
fileName = "frps_linux_arm64"
case runtime.GOOS == "linux" && runtime.GOARCH == "arm":
fileName = "frps_linux_arm_hf"
default:
path, err := exec.LookPath("frps")
if err == nil {
cachedFrpsPath = path
return path, nil
}
return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH)
}
localPath := filepath.Join(".", "bin", fileName)
if _, err := os.Stat(localPath); err == nil {
cachedFrpsPath = localPath
return localPath, nil
}
data, err := embeddedFrps.ReadFile("bin/" + fileName)
if err == nil {
tmpPath := filepath.Join(os.TempDir(), "frps")
if runtime.GOOS == "windows" {
tmpPath += ".exe"
}
if err := os.WriteFile(tmpPath, data, 0755); err == nil {
cachedFrpsPath = tmpPath
return tmpPath, nil
}
if _, statErr := os.Stat(tmpPath); statErr == nil {
cachedFrpsPath = tmpPath
return tmpPath, nil
}
}
path, err := exec.LookPath("frps")
if err == nil {
cachedFrpsPath = path
return path, nil
}
return "", fmt.Errorf("未找到 frps 文件")
}
// ============================================================
// 生成 frps.toml
// ============================================================
func GenerateFrpsConfig() error {
cfg, err := GetGlobalConfig()
if err != nil {
return fmt.Errorf("读取全局配置失败: %w", err)
}
// 直接传递 cfg 即可,模板里能读到 .WireProtocolV2
var tmplContent string
if _, err := os.Stat("frps.tmpl"); err == nil {
content, readErr := os.ReadFile("frps.tmpl")
if readErr == nil {
tmplContent = string(content)
} else {
tmplContent = FrpsTemplateContent
}
} else {
tmplContent = FrpsTemplateContent
}
tmpl, err := template.New("frps").Parse(tmplContent)
if err != nil {
return fmt.Errorf("解析模板失败: %w", err)
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, cfg); err != nil {
return fmt.Errorf("渲染模板失败: %w", err)
}
if err := os.WriteFile("./frps.toml", buf.Bytes(), 0644); err != nil {
return fmt.Errorf("写入配置文件失败: %w", err)
}
return nil
}
// ============================================================
// frps 进程管理
// ============================================================
func isFrpsRunning() bool {
pidData, err := os.ReadFile("./frps.pid")
if err != nil {
return false
}
pid, err := strconv.Atoi(strings.TrimSpace(string(pidData)))
if err != nil {
return false
}
if runtime.GOOS == "windows" {
cmd := exec.Command("tasklist", "/FI", "PID eq", strconv.Itoa(pid))
output, err := cmd.CombinedOutput()
if err != nil {
return false
}
return strings.Contains(string(output), strconv.Itoa(pid))
}
process, err := os.FindProcess(pid)
if err != nil {
return false
}
return process.Signal(syscall.Signal(0)) == nil
}
func StartFrps() error {
frpsPath, err := getFrpsPath()
if err != nil {
return fmt.Errorf("获取 frps 路径失败: %w", err)
}
if _, err := os.Stat("./frps.toml"); os.IsNotExist(err) {
if err := GenerateFrpsConfig(); err != nil {
return fmt.Errorf("生成配置文件失败: %w", err)
}
}
if isFrpsRunning() {
return nil
}
os.Remove("./frps.pid")
cmd := exec.Command(frpsPath, "-c", "./frps.toml")
setWindowHide(cmd)
setSysProcAttr(cmd)
logFile, err := os.OpenFile("./frps.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return fmt.Errorf("打开日志文件失败: %w", err)
}
cmd.Stdout = logFile
cmd.Stderr = logFile
if err := cmd.Start(); err != nil {
return fmt.Errorf("启动 frps 失败: %w", err)
}
if err := os.WriteFile("./frps.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil {
return fmt.Errorf("保存 PID 失败: %w", err)
}
return nil
}
func StopFrps() error {
if runtime.GOOS == "windows" {
cmd := exec.Command("taskkill", "/F", "/IM", "frps.exe")
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") {
return fmt.Errorf("停止 frps 失败: %w", err)
}
os.Remove("./frps.pid")
return nil
}
pidData, err := os.ReadFile("./frps.pid")
if err != nil {
cmd := exec.Command("pkill", "-f", "frps")
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") {
return fmt.Errorf("停止 frps 失败: %w", err)
}
return nil
}
pid, _ := strconv.Atoi(strings.TrimSpace(string(pidData)))
process, err := os.FindProcess(pid)
if err != nil {
os.Remove("./frps.pid")
return nil
}
if err := process.Kill(); err != nil {
return fmt.Errorf("杀死进程失败: %w", err)
}
os.Remove("./frps.pid")
return nil
}
func GetFrpsStatus() (bool, error) {
return isFrpsRunning(), nil
}
func ReloadFrps() error {
running := isFrpsRunning()
if !running {
return StartFrps()
}
frpsPath, err := getFrpsPath()
if err != nil {
return fmt.Errorf("获取 frps 路径失败: %w", err)
}
cmd := exec.Command(frpsPath, "reload", "-c", "./frps.toml")
_, err = cmd.CombinedOutput()
if err != nil {
log.Printf("⚠️ 热加载失败 (%v),自动降级为重启 frps", err)
if stopErr := StopFrps(); stopErr != nil {
return fmt.Errorf("停止 frps 失败: %w", stopErr)
}
if startErr := StartFrps(); startErr != nil {
return fmt.Errorf("启动 frps 失败: %w", startErr)
}
return nil
}
return nil
}