配置全自动docker版本部署
This commit is contained in:
@@ -4,17 +4,16 @@ import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
//go:embed bin/*
|
||||
@@ -36,7 +35,6 @@ func getFrpcPath() (string, error) {
|
||||
frpcPathMutex.Lock()
|
||||
defer frpcPathMutex.Unlock()
|
||||
|
||||
// 如果缓存有效,直接返回
|
||||
if cachedFrpcPath != "" {
|
||||
if _, err := os.Stat(cachedFrpcPath); err == nil {
|
||||
return cachedFrpcPath, nil
|
||||
@@ -44,7 +42,6 @@ func getFrpcPath() (string, error) {
|
||||
cachedFrpcPath = ""
|
||||
}
|
||||
|
||||
// 确定当前平台的文件名
|
||||
var fileName string
|
||||
switch {
|
||||
case runtime.GOOS == "windows" && runtime.GOARCH == "amd64":
|
||||
@@ -58,7 +55,6 @@ func getFrpcPath() (string, error) {
|
||||
case runtime.GOOS == "linux" && runtime.GOARCH == "arm":
|
||||
fileName = "frpc_linux_armv7"
|
||||
default:
|
||||
// 不支持的平台,尝试从 PATH 找
|
||||
path, err := exec.LookPath("frpc")
|
||||
if err == nil {
|
||||
cachedFrpcPath = path
|
||||
@@ -67,14 +63,14 @@ func getFrpcPath() (string, error) {
|
||||
return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// ===== 优先级 1:从当前目录 ./bin/ 查找 =====
|
||||
// 优先级1:本地 ./bin/
|
||||
localPath := filepath.Join(".", "bin", fileName)
|
||||
if _, err := os.Stat(localPath); err == nil {
|
||||
cachedFrpcPath = localPath
|
||||
return localPath, nil
|
||||
}
|
||||
|
||||
// ===== 优先级 2:从 embed 读取并解压到临时目录 =====
|
||||
// 优先级2:embed 解压到临时目录
|
||||
data, err := embeddedFrpc.ReadFile("bin/" + fileName)
|
||||
if err == nil {
|
||||
tmpPath := filepath.Join(os.TempDir(), "frpc")
|
||||
@@ -85,14 +81,13 @@ func getFrpcPath() (string, error) {
|
||||
cachedFrpcPath = tmpPath
|
||||
return tmpPath, nil
|
||||
}
|
||||
// 写入失败时,检查是否已有可用的临时文件
|
||||
if _, statErr := os.Stat(tmpPath); statErr == nil {
|
||||
cachedFrpcPath = tmpPath
|
||||
return tmpPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 优先级 3:降级到系统 PATH =====
|
||||
// 优先级3:系统 PATH
|
||||
path, err := exec.LookPath("frpc")
|
||||
if err == nil {
|
||||
cachedFrpcPath = path
|
||||
@@ -102,7 +97,7 @@ func getFrpcPath() (string, error) {
|
||||
return "", fmt.Errorf("未找到 frpc 文件 (本地 bin/、embed、PATH 均无)")
|
||||
}
|
||||
|
||||
// GenerateFrpcConfig 生成 frpc.toml 配置文件
|
||||
// GenerateFrpcConfig 生成 frpc.toml
|
||||
func GenerateFrpcConfig() error {
|
||||
cfg, err := GetGlobalConfig()
|
||||
if err != nil {
|
||||
@@ -129,7 +124,6 @@ func GenerateFrpcConfig() error {
|
||||
Proxies: activeProxies,
|
||||
}
|
||||
|
||||
// 优先使用外部模板文件,不存在则用 embed
|
||||
var tmplContent string
|
||||
if _, err := os.Stat("frpc.tmpl"); err == nil {
|
||||
content, readErr := os.ReadFile("frpc.tmpl")
|
||||
@@ -159,8 +153,36 @@ func GenerateFrpcConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFrpc 启动 frpc
|
||||
// isFrpcRunning 通过 PID 文件检查 frpc 是否在运行
|
||||
func isFrpcRunning() bool {
|
||||
pidData, err := os.ReadFile("./frpc.pid")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
pid, err := strconv.Atoi(strings.TrimSpace(string(pidData)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows 用 tasklist 检查
|
||||
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))
|
||||
}
|
||||
|
||||
// Linux/Unix: 用 kill 0 检查进程是否存在
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return process.Signal(syscall.Signal(0)) == nil
|
||||
}
|
||||
|
||||
// StartFrpc 启动 frpc(独立进程,脱离父进程)
|
||||
func StartFrpc() error {
|
||||
frpcPath, err := getFrpcPath()
|
||||
if err != nil {
|
||||
@@ -173,37 +195,84 @@ func StartFrpc() error {
|
||||
}
|
||||
}
|
||||
|
||||
running, _ := GetFrpcStatus()
|
||||
if running {
|
||||
if isFrpcRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 清理旧的 PID 文件
|
||||
os.Remove("./frpc.pid")
|
||||
|
||||
cmd := exec.Command(frpcPath, "-c", "./frpc.toml")
|
||||
setWindowHide(cmd)
|
||||
|
||||
// ✅ Linux 下分配伪终端,让 frpc 认为自己在真实终端中运行
|
||||
if runtime.GOOS != "windows" {
|
||||
f, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("启动伪终端失败: %w", err)
|
||||
}
|
||||
// 可选:丢弃 pty 输出,或写入日志文件
|
||||
go io.Copy(io.Discard, f)
|
||||
} else {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("启动 frpc 失败: %w", err)
|
||||
}
|
||||
// ✅ 设置进程属性(Unix: Setsid 脱离父进程,Windows: 不处理)
|
||||
setSysProcAttr(cmd)
|
||||
|
||||
// 把输出重定向到日志文件
|
||||
logFile, err := os.OpenFile("./frpc.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("启动 frpc 失败: %w", err)
|
||||
}
|
||||
|
||||
// ✅ 保存 PID 到文件
|
||||
if err := os.WriteFile("./frpc.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil {
|
||||
return fmt.Errorf("保存 PID 失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopFrpc 停止 frpc(通过 PID 文件精准杀进程)
|
||||
func StopFrpc() error {
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command("taskkill", "/F", "/IM", "frpc.exe")
|
||||
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") {
|
||||
return fmt.Errorf("停止 frpc 失败: %w", err)
|
||||
}
|
||||
os.Remove("./frpc.pid")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Linux: 读取 PID 文件精准杀进程
|
||||
pidData, err := os.ReadFile("./frpc.pid")
|
||||
if err != nil {
|
||||
// 降级到 pkill
|
||||
cmd := exec.Command("pkill", "-f", "frpc")
|
||||
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") {
|
||||
return fmt.Errorf("停止 frpc 失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
pid, _ := strconv.Atoi(strings.TrimSpace(string(pidData)))
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
os.Remove("./frpc.pid")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := process.Kill(); err != nil {
|
||||
return fmt.Errorf("杀死进程失败: %w", err)
|
||||
}
|
||||
|
||||
os.Remove("./frpc.pid")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFrpcStatus 获取 frpc 运行状态(通过 PID 文件)
|
||||
func GetFrpcStatus() (bool, error) {
|
||||
return isFrpcRunning(), nil
|
||||
}
|
||||
|
||||
// ReloadFrpc 热加载,失败时自动降级为重启
|
||||
func ReloadFrpc() error {
|
||||
running, err := GetFrpcStatus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查 frpc 状态失败: %w", err)
|
||||
}
|
||||
running := isFrpcRunning()
|
||||
if !running {
|
||||
return StartFrpc()
|
||||
}
|
||||
@@ -227,37 +296,3 @@ func ReloadFrpc() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopFrpc 停止 frpc
|
||||
func StopFrpc() error {
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command("taskkill", "/F", "/IM", "frpc.exe")
|
||||
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") {
|
||||
return fmt.Errorf("停止 frpc 失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
cmd := exec.Command("pkill", "-f", "frpc")
|
||||
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") {
|
||||
return fmt.Errorf("停止 frpc 失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFrpcStatus 检查 frpc 是否在运行
|
||||
func GetFrpcStatus() (bool, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq frpc.exe")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return strings.Contains(string(output), "frpc.exe"), nil
|
||||
}
|
||||
cmd := exec.Command("pgrep", "-f", "frpc")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return len(output) > 0, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user