Browse Source

add: frpc logic

master
pcong 5 months ago
parent
commit
31cce7c349
  1. 4
      godo/.gitignore
  2. 65
      godo/deps/app_embed.go
  3. 86
      godo/deps/extract.go
  4. 42
      godo/libs/osinfo.go
  5. 12
      godo/progress/linux.go
  6. 149
      godo/progress/progress.go
  7. 15
      godo/progress/windows.go

4
godo/.gitignore

@ -3,3 +3,7 @@ deps/dist
deps/*.zip
godo
testdata
deps/darwin/*.zip
deps/linux/*.zip
deps/windows/*.zip
.DS_Store

65
godo/deps/app_embed.go

@ -0,0 +1,65 @@
package deps
import (
_ "embed"
"errors"
"godo/libs"
)
//go:embed darwin/amd64.zip
var embeddedDarwinAMD64Zip []byte
//go:embed darwin/arm64.zip
var embeddedDarwinARM64Zip []byte
//go:embed linux/amd64.zip
var embeddedLinuxAMD64Zip []byte
//go:embed linux/arm64.zip
var embeddedLinuxARM64Zip []byte
//go:embed windows/amd64.zip
var embeddedWindowsAMD64Zip []byte
//go:embed windows/arm64.zip
var embeddedWindowsARM64Zip []byte
const (
FRPCAPP = "frpc"
)
var (
ErrUnsupportedOSArch = errors.New("unsupported OS and architecture combination")
embeddedZips = map[string]map[string]map[string][]byte{
libs.OSDarwin: {
libs.ArchAMD64: {FRPCAPP: embeddedDarwinAMD64Zip},
libs.ArchARM64: {FRPCAPP: embeddedDarwinARM64Zip},
libs.ArchARM: {FRPCAPP: embeddedDarwinARM64Zip},
},
libs.OSWindows: {
libs.ArchAMD64: {FRPCAPP: embeddedWindowsAMD64Zip},
libs.ArchARM64: {FRPCAPP: embeddedWindowsARM64Zip},
libs.ArchARM: {FRPCAPP: embeddedWindowsARM64Zip},
},
libs.OSLinux: {
libs.ArchAMD64: {FRPCAPP: embeddedLinuxAMD64Zip},
libs.ArchARM64: {FRPCAPP: embeddedLinuxARM64Zip},
libs.ArchARM: {FRPCAPP: embeddedLinuxARM64Zip},
},
}
)
func ExtractZip(appName, targetDir string) error {
os, arch := libs.GetOSAndArch()
archMap, ok := embeddedZips[os]
if !ok {
return ErrUnsupportedOSArch
}
zipData, ok := archMap[arch][appName]
if !ok {
return ErrUnsupportedOSArch
}
return extractZip(zipData, targetDir)
}

86
godo/deps/extract.go

@ -0,0 +1,86 @@
package deps
import (
"archive/zip"
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)
// extractZip 解压嵌入的 zip 文件到目标目录
func extractZip(zipData []byte, targetDir string) error {
log.Println("Extracting embedded ZIP file...")
// 使用内存缓冲区来读取嵌入的ZIP数据
reader := bytes.NewReader(zipData)
zipReader, err := zip.NewReader(reader, int64(len(zipData)))
if err != nil {
return fmt.Errorf("failed to create zip reader: %v", err)
}
// 遍历ZIP文件中的每个条目并解压
for _, zipEntry := range zipReader.File {
// 忽略 __MACOSX 文件夹
if strings.HasPrefix(zipEntry.Name, "__MACOSX") {
continue
}
// 检查条目名称是否以"."开头,如果是,则跳过
if strings.HasPrefix(zipEntry.Name, ".") {
fmt.Printf("Skipping hidden entry: %s\n", zipEntry.Name)
continue
}
// 去掉 ZIP 文件中的顶层目录(zip文件名本身)
entryName := zipEntry.Name
if idx := strings.Index(entryName, "/"); idx != -1 {
entryName = entryName[idx+1:] // 去掉顶层目录
}
// 如果 entryName 为空,说明是顶层目录本身,跳过
if entryName == "" {
continue
}
// 构建解压后的文件或目录路径
entryPath := filepath.Join(targetDir, entryName)
fmt.Println("Extracting:", entryName)
// 如果是目录,则创建目录
if zipEntry.FileInfo().IsDir() {
if err := os.MkdirAll(entryPath, zipEntry.Mode()); err != nil {
return fmt.Errorf("failed to create directory: %v", err)
}
continue
}
// 如果是文件,则解压文件
zipFile, err := zipEntry.Open()
if err != nil {
return fmt.Errorf("failed to open zip file entry: %v", err)
}
defer zipFile.Close()
// 确保目标文件的父目录存在
if err := os.MkdirAll(filepath.Dir(entryPath), 0755); err != nil {
return fmt.Errorf("failed to create parent directory: %v", err)
}
dstFile, err := os.OpenFile(entryPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return fmt.Errorf("failed to create destination file: %v", err)
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, zipFile); err != nil {
return fmt.Errorf("failed to copy content to destination file: %v", err)
}
}
log.Println("Embedded ZIP extracted to", targetDir)
return nil
}

42
godo/libs/osinfo.go

@ -0,0 +1,42 @@
package libs
import "runtime"
const (
OSDarwin = "macOS"
OSWindows = "Windows"
OSLinux = "Linux"
ArchAMD64 = "x86_64"
ArchARM64 = "ARM64"
ArchARM = "ARM"
)
// GetOSAndArch 返回当前操作系统的名称和架构的名称
func GetOSAndArch() (osName, archName string) {
// 获取操作系统
switch runtime.GOOS {
case "darwin":
osName = OSDarwin
case "windows":
osName = OSWindows
case "linux":
osName = OSLinux
default:
osName = "Unknown OS"
}
// 获取架构
switch runtime.GOARCH {
case "amd64":
archName = ArchAMD64
case "arm64":
archName = ArchARM64
case "arm":
archName = ArchARM
default:
archName = "Unknown Architecture"
}
return osName, archName
}

12
godo/progress/linux.go

@ -0,0 +1,12 @@
//go:build !windows
package progress
import (
// 导入包
"os/exec"
)
func SetHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
return cmd
}

149
godo/progress/progress.go

@ -0,0 +1,149 @@
package progress
import (
"errors"
"fmt"
"godo/deps"
"godo/libs"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
)
type Process struct {
Name string
Running bool
ExitCode int
Cmd *exec.Cmd
}
var (
processesMu sync.RWMutex
processes = make(map[string]*Process)
)
func RegisterProcess(name string, cmdstr *exec.Cmd) {
processesMu.Lock()
defer processesMu.Unlock()
processes[name] = &Process{
Name: name,
Running: true,
Cmd: cmdstr,
}
}
func GetCmd(name string) *Process {
processesMu.Lock()
defer processesMu.Unlock()
return processes[name]
}
// StartCmd 执行指定名称的脚本。
// 参数:
// name - 脚本的名称。
// 返回值:
// 返回可能遇到的错误,如果执行成功,则返回nil。
func StartCmd(name string) error {
info, ok := processes[name]
if ok && info.Running {
return fmt.Errorf("process information for '%s' is runing", name)
}
path := name
appName := name
scriptPath, err := libs.GetCmdPath(path, name)
if err != nil {
// 不存在,解压应用
switch name {
case deps.FRPCAPP:
appName = deps.FRPCAPP
default:
return errors.New("app not found")
}
err = deps.ExtractZip(appName, libs.GetAppExecDir())
if err != nil {
return fmt.Errorf("failed to extract zip file: %v", err)
}
}
// 设置并启动脚本执行命令
var cmd *exec.Cmd
switch name {
case "frpc":
// 检查配置文件
configPath := filepath.Join(libs.GetAppExecDir(), appName, "frpc.ini")
if !libs.PathExists(configPath) {
return fmt.Errorf("frpc config file not found")
}
params := []string{
"-c",
configPath,
}
cmd = exec.Command(scriptPath, params...)
default:
cmd = exec.Command(scriptPath)
}
if runtime.GOOS == "windows" {
// 在Windows上,通过设置CreationFlags来隐藏窗口
cmd = SetHideConsoleCursor(cmd)
}
go func() {
// 启动脚本命令并返回可能的错误
if err := cmd.Start(); err != nil {
log.Printf("failed to start process %s: %v", name, err)
return
}
RegisterProcess(name, cmd)
// 等待命令完成
if err := cmd.Wait(); err != nil {
log.Printf("command failed for %s: %v", name, err)
return
} else {
log.Printf("%s command completed successfully", name)
}
// 命令完成后,更新进程信息
processesMu.Lock()
defer processesMu.Unlock()
if p, ok := processes[name]; ok {
p.Running = false
p.ExitCode = cmd.ProcessState.ExitCode()
}
}()
return nil
}
func StopCmd(name string) error {
cmd, ok := processes[name]
if !ok {
return fmt.Errorf("process information for '%s' not found", name)
}
// 停止进程并更新status
if err := cmd.Cmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to kill process %s: %v", name, err)
}
//delete(processes, name) // 更新status,表示进程已停止
cmd.Running = false
return nil
}
func RestartCmd(name string) error {
if err := StopCmd(name); err != nil {
return err
}
return StartCmd(name)
}
func StopAllCmd() error {
processesMu.Lock()
defer processesMu.Unlock()
for name, cmd := range processes {
if err := cmd.Cmd.Process.Signal(os.Interrupt); err != nil {
return fmt.Errorf("failed to stop process %s: %v", name, err)
}
}
return nil
}

15
godo/progress/windows.go

@ -0,0 +1,15 @@
//go:build windows
// +build windows
package progress
import (
// 导入包
"os/exec"
"syscall"
)
func SetHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
return cmd
}
Loading…
Cancel
Save