diff --git a/godo/cmd/main.go b/godo/cmd/main.go
index 14383d7..9b3a5c8 100644
--- a/godo/cmd/main.go
+++ b/godo/cmd/main.go
@@ -6,6 +6,7 @@ import (
"godo/libs"
"godo/localchat"
"godo/progress"
+ "godo/store"
"godo/sys"
"log"
"net/http"
@@ -41,13 +42,16 @@ func OsStart() {
progressRouter.HandleFunc("/list", progress.Status).Methods(http.MethodGet)
progressRouter.HandleFunc("/listport", progress.ListPortsHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/killport", progress.KillPortHandler).Methods(http.MethodGet)
- progressRouter.HandleFunc("/app/{name}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
- progressRouter.HandleFunc("/app/{name}/{subpath:.*}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
- router.HandleFunc("/system/storeList", sys.GetStoreInfoHandler).Methods(http.MethodGet)
router.HandleFunc("/system/setting", sys.HandleSetConfig).Methods(http.MethodPost)
+
+ router.HandleFunc("/store/list", store.GetStoreInfoHandler).Methods(http.MethodGet)
+ router.HandleFunc("/store/download", store.DownloadHandler).Methods(http.MethodGet)
+ router.HandleFunc("/store/install", store.InstallHandler).Methods(http.MethodGet)
+ router.HandleFunc("/store/uninstall", store.UnInstallHandler).Methods(http.MethodGet)
+
router.HandleFunc("/files/info", files.HandleSystemInfo).Methods(http.MethodGet)
router.HandleFunc("/file/read", files.HandleReadDir).Methods(http.MethodGet)
router.HandleFunc("/file/stat", files.HandleStat).Methods(http.MethodGet)
diff --git a/godo/files/unzip.go b/godo/files/unzip.go
new file mode 100644
index 0000000..8e358e9
--- /dev/null
+++ b/godo/files/unzip.go
@@ -0,0 +1,165 @@
+package files
+
+import (
+ "compress/bzip2"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "archive/tar"
+ "archive/zip"
+)
+
+// Decompress 解压文件到指定目录,支持多种压缩格式
+func Decompress(compressedFilePath, destPath string) error {
+ ext := filepath.Ext(compressedFilePath)
+ switch ext {
+ case ".zip":
+ return Unzip(compressedFilePath, destPath)
+ case ".tar":
+ tarFile, err := os.Open(compressedFilePath)
+ if err != nil {
+ return err
+ }
+ defer tarFile.Close()
+ return Untar(tarFile, destPath)
+ case ".gz":
+ return Untargz(compressedFilePath, destPath)
+ case ".bz2":
+ return Untarbz2(compressedFilePath, destPath)
+ default:
+ return fmt.Errorf("unsupported compression format: %s", ext)
+ }
+}
+
+// Untar 解压.tar文件到指定目录
+func Untar(reader io.Reader, destPath string) error {
+ tarReader := tar.NewReader(reader)
+ for {
+ header, err := tarReader.Next()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return err
+ }
+
+ target := filepath.Join(destPath, header.Name)
+ if header.Typeflag == tar.TypeDir {
+ if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
+ return err
+ }
+ continue
+ }
+
+ outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode))
+ if err != nil {
+ return err
+ }
+ defer outFile.Close()
+
+ if _, err := io.Copy(outFile, tarReader); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Untargz 解压.tar.gz文件到指定目录
+func Untargz(targzFilePath, destPath string) error {
+ gzFile, err := os.Open(targzFilePath)
+ if err != nil {
+ return err
+ }
+ defer gzFile.Close()
+
+ gzReader, err := gzip.NewReader(gzFile)
+ if err != nil {
+ return err
+ }
+ defer gzReader.Close()
+
+ return Untar(gzReader, destPath) // 传递gzReader给Untar
+}
+
+// Untarbz2 解压.tar.bz2文件到指定目录
+func Untarbz2(tarbz2FilePath, destPath string) error {
+ bz2File, err := os.Open(tarbz2FilePath)
+ if err != nil {
+ return err
+ }
+ defer bz2File.Close()
+
+ bz2Reader := bzip2.NewReader(bz2File)
+ return Untar(bz2Reader, destPath) // 传递bz2Reader给Untar
+}
+
+// DecompressZip 解压zip文件到指定目录
+func Unzip(zipFilePath, destPath string) error {
+ r, err := zip.OpenReader(zipFilePath)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ for _, f := range r.File {
+ rc, err := f.Open()
+ if err != nil {
+ return err
+ }
+ defer rc.Close()
+
+ path := filepath.Join(destPath, f.Name)
+
+ if f.FileInfo().IsDir() {
+ os.MkdirAll(path, f.Mode())
+ continue
+ }
+
+ os.MkdirAll(filepath.Dir(path), f.Mode())
+
+ outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+ if err != nil {
+ return err
+ }
+ defer outFile.Close()
+
+ _, err = io.Copy(outFile, rc)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// IsSupportedCompressionFormat 判断文件后缀是否为支持的压缩格式
+func IsSupportedCompressionFormat(filename string) bool {
+ supportedFormats := map[string]bool{
+ ".zip": true,
+ ".tar": true,
+ ".gz": true,
+ ".bz2": true,
+ }
+
+ for format := range supportedFormats {
+ if strings.HasSuffix(strings.ToLower(filename), format) {
+ return true
+ }
+ }
+ return false
+}
+
+// HandlerFile 根据文件后缀判断是否支持解压,支持则解压,否则移动文件
+func HandlerFile(filePath string, destDir string) error {
+ if !IsSupportedCompressionFormat(filePath) {
+ // 移动文件
+ newPath := filepath.Join(destDir, filepath.Base(filePath))
+ return os.Rename(filePath, newPath)
+ }
+ // 解压文件
+ return Decompress(filePath, destDir)
+}
diff --git a/godo/files/zip.go b/godo/files/zip.go
new file mode 100644
index 0000000..d10fbb1
--- /dev/null
+++ b/godo/files/zip.go
@@ -0,0 +1,219 @@
+package files
+
+import (
+ "archive/tar"
+ "archive/zip"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// 压缩文件到指定的目录
+func zipCompress(folderPath, zipFilePath string) error {
+ zipFile, err := os.Create(zipFilePath)
+ if err != nil {
+ return err
+ }
+ defer zipFile.Close()
+
+ zipWriter := zip.NewWriter(zipFile)
+ defer zipWriter.Close()
+
+ info, err := os.Stat(folderPath)
+ if err != nil {
+ return err
+ }
+
+ var baseDir string
+ if info.IsDir() {
+ baseDir = filepath.Base(folderPath)
+ }
+
+ filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ header, err := zip.FileInfoHeader(info)
+ if err != nil {
+ return err
+ }
+
+ relPath, err := filepath.Rel(folderPath, path)
+ if err != nil {
+ return err
+ }
+
+ if baseDir != "" {
+ header.Name = filepath.Join(baseDir, relPath)
+ } else {
+ header.Name = relPath
+ }
+
+ if info.IsDir() {
+ header.Name += "/"
+ } else {
+ header.Method = zip.Deflate
+ }
+
+ writer, err := zipWriter.CreateHeader(header)
+ if err != nil {
+ return err
+ }
+
+ if info.IsDir() {
+ return nil
+ }
+
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ _, err = io.Copy(writer, file)
+ return err
+ })
+
+ return nil
+}
+
+// IsSupportedCompressionFormat 判断文件后缀是否为支持的压缩格式
+func IsSupportedFormat(filename string) bool {
+ supportedFormats := map[string]bool{
+ ".zip": true,
+ ".tar": true,
+ ".gz": true,
+ ".bz2": true,
+ // 添加其他支持的复合格式
+ }
+
+ for format := range supportedFormats {
+ if strings.HasSuffix(strings.ToLower(filename), format) {
+ return true
+ }
+ }
+ return false
+}
+
+// CompressFileOrFolder 压缩文件或文件夹到指定的压缩文件
+func CompressFileOrFolder(sourcePath, compressedFilePath string) error {
+ if IsSupportedFormat(compressedFilePath) {
+ return Encompress(sourcePath, compressedFilePath)
+ }
+ // 如果压缩文件格式不受支持,则返回错误或采取其他措施
+ return fmt.Errorf("unsupported compression format for file: %s", compressedFilePath)
+}
+
+// Encompress 压缩文件或文件夹到指定的压缩格式
+func Encompress(sourcePath, compressedFilePath string) error {
+ switch filepath.Ext(compressedFilePath) {
+ case ".zip":
+ return zipCompress(sourcePath, compressedFilePath)
+ case ".tar":
+ return tarCompress(sourcePath, compressedFilePath)
+ case ".gz":
+ return tarGzCompress(sourcePath, compressedFilePath)
+ default:
+ return fmt.Errorf("unsupported compression format: %s", filepath.Ext(compressedFilePath))
+ }
+}
+
+// tarCompress 使用 tar 格式压缩文件或文件夹
+func tarCompress(folderPath, tarFilePath string) error {
+ // 打开目标文件
+ tarFile, err := os.Create(tarFilePath)
+ if err != nil {
+ return err
+ }
+ defer tarFile.Close()
+
+ // 创建 tar.Writer
+ tarWriter := tar.NewWriter(tarFile)
+ defer tarWriter.Close()
+
+ // 遍历文件夹并添加到 tar 文件
+ err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ header, err := tar.FileInfoHeader(info, "")
+ if err != nil {
+ return err
+ }
+
+ if err := tarWriter.WriteHeader(header); err != nil {
+ return err
+ }
+
+ if !info.IsDir() {
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ _, err = io.Copy(tarWriter, file)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+
+ return err
+}
+
+// tarGzCompress 使用 tar.gz 格式压缩文件或文件夹
+func tarGzCompress(folderPath, tarGzFilePath string) error {
+ // 打开目标文件
+ gzFile, err := os.Create(tarGzFilePath)
+ if err != nil {
+ return err
+ }
+ defer gzFile.Close()
+
+ // 创建 gzip.Writer
+ gzWriter := gzip.NewWriter(gzFile)
+ defer gzWriter.Close()
+
+ // 创建 tar.Writer
+ tarWriter := tar.NewWriter(gzWriter)
+ defer tarWriter.Close()
+
+ // 遍历文件夹并添加到 tar 文件
+ err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ header, err := tar.FileInfoHeader(info, "")
+ if err != nil {
+ return err
+ }
+
+ if err := tarWriter.WriteHeader(header); err != nil {
+ return err
+ }
+
+ if !info.IsDir() {
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ _, err = io.Copy(tarWriter, file)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+
+ return err
+}
diff --git a/godo/go.mod b/godo/go.mod
index 029314b..9b19ddc 100644
--- a/godo/go.mod
+++ b/godo/go.mod
@@ -3,11 +3,9 @@ module godo
go 1.22.5
require (
- github.com/cavaliergopher/grab/v3 v3.0.1
github.com/fsnotify/fsnotify v1.7.0
github.com/gorilla/mux v1.8.1
github.com/minio/selfupdate v0.6.0
- github.com/pkg/errors v0.9.1
github.com/shirou/gopsutil v3.21.11+incompatible
)
diff --git a/godo/go.sum b/godo/go.sum
index d04d61e..5185936 100644
--- a/godo/go.sum
+++ b/godo/go.sum
@@ -1,7 +1,5 @@
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
-github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
-github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -12,8 +10,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
diff --git a/godo/localchat/client.go b/godo/localchat/client.go
index f582b3d..380adcb 100644
--- a/godo/localchat/client.go
+++ b/godo/localchat/client.go
@@ -42,7 +42,7 @@ func DiscoverServers() {
conn.Close()
continue
}
- log.Printf("Sending message: %+v", msg)
+ //log.Printf("Sending message: %+v", msg)
_, err = conn.Write(jsonData)
if err != nil {
fmt.Println(err)
diff --git a/godo/localchat/server.go b/godo/localchat/server.go
index 555bbbd..5e9cc33 100644
--- a/godo/localchat/server.go
+++ b/godo/localchat/server.go
@@ -27,10 +27,10 @@ func StartServiceDiscovery() {
for {
n, addr, err := conn.ReadFromUDP(buffer)
if err != nil {
- log.Printf("Error reading from UDP: %v", err)
+ log.Printf("Error reading from UDP: %v,addr:%v", err, addr)
continue
}
- fmt.Printf("Received message: %s from %s\n", buffer[:n], addr)
+ //fmt.Printf("Received message: %s from %s\n", buffer[:n], addr)
var udpMsg UdpMessage
err = json.Unmarshal(buffer[:n], &udpMsg)
@@ -38,7 +38,7 @@ func StartServiceDiscovery() {
fmt.Printf("Error unmarshalling JSON: %v\n", err)
continue
}
- log.Printf("Get message: %+v", udpMsg)
+ //log.Printf("Get message: %+v", udpMsg)
OnlineUsers[udpMsg.IP] = udpMsg
}
}
diff --git a/godo/progress/checkactive.go b/godo/progress/checkactive.go
index 633d15b..86dc44e 100644
--- a/godo/progress/checkactive.go
+++ b/godo/progress/checkactive.go
@@ -2,10 +2,14 @@ package progress
import (
"context"
+ "fmt"
"log"
- "net/http"
"os"
+ "os/exec"
+ "runtime"
+ "strings"
"sync"
+ "syscall"
"time"
)
@@ -34,37 +38,46 @@ func checkInactiveProcesses() {
defer processesMu.RUnlock()
for name, p := range processes {
- // 检查进程是否还在运行
pid := p.Cmd.Process.Pid
_, err := os.FindProcess(pid)
if err != nil {
- p.Running = false
- // 进程已不存在,跳过后续检查
+ if os.IsNotExist(err) {
+ p.Running = false
+ log.Printf("Process %s (PID: %d) not found.", name, pid)
+ } else {
+ log.Printf("Error finding process %s (PID: %d): %v", name, pid, err)
+ }
continue
}
- // 如果进程还在运行,检查/ping接口
- if p.Running {
- resp, err := http.Get(p.PingURL)
- if err != nil {
- p.Running = false
- // 进程未响应
- log.Printf("Error checking ping for process %s: %v", name, err)
- continue
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- p.Running = false
- // 进程未正常响应,停止进程
- if err := p.Cmd.Process.Signal(os.Kill); err != nil {
- log.Printf("Failed to stop process %s before restart: %v", name, err)
- continue
- }
- } else {
- // 进程正常响应,更新LastPingAt并继续检查下一个进程
- p.LastPing = time.Now()
- }
- }
+ // 进程仍然运行,更新LastPing
+ p.LastPing = time.Now()
}
}
+
+func IsProcessRunning(appName string) (bool, error) {
+ var cmd *exec.Cmd
+ switch runtime.GOOS {
+ case "windows":
+ cmd = exec.Command("powershell", "Get-Process", appName)
+ case "linux":
+ fallthrough
+ case "darwin":
+ cmd = exec.Command("pgrep", "-f", appName)
+ default:
+ return false, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
+ }
+
+ var output []byte
+ var err error
+ if cmd.SysProcAttr == nil {
+ cmd.SysProcAttr = &syscall.SysProcAttr{}
+ }
+ cmd.SysProcAttr.HideWindow = true // For Windows to hide the console window
+
+ if output, err = cmd.CombinedOutput(); err != nil {
+ return false, fmt.Errorf("error checking process: %w", err)
+ }
+
+ return len(strings.TrimSpace(string(output))) > 0, nil
+}
diff --git a/godo/progress/forward.go b/godo/progress/forward.go
deleted file mode 100644
index 26ca87f..0000000
--- a/godo/progress/forward.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package progress
-
-import (
- "fmt"
- "log"
- "net/http"
- "net/url"
- "path"
- "time"
-
- "github.com/gorilla/mux"
-)
-
-func ForwardRequest(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- proxyName := vars["name"] // 获取代理名称
- log.Printf("Forwarding request to proxy %s", proxyName)
- log.Printf("processes: %+v", processes)
- cmd, ok := processes[proxyName]
- if !ok || !cmd.Running {
- // Start the process again
- err := ExecuteScript(proxyName)
- if err != nil {
- respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart process %s: %v", proxyName, err))
- return
- }
- time.Sleep(1 * time.Second)
- }
- if cmd.PingURL == "" {
- respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get url %s", proxyName))
- return
- }
- // 构造目标基础URL,假设proxyName直接对应目标服务的基础路径
- targetBaseURLStr := cmd.PingURL
- targetBaseURL, err := url.Parse(targetBaseURLStr)
- if err != nil {
- respondWithError(w, http.StatusInternalServerError, "Failed to parse target base URL")
- return
- }
- // 获取请求的子路径
- subpath := r.URL.Path[len("/"+proxyName):]
-
- targetURL := &url.URL{
- Scheme: targetBaseURL.Scheme,
- Host: targetBaseURL.Host,
- Path: path.Clean(path.Join(targetBaseURL.Path, subpath)), // 使用path.Clean处理路径
- RawQuery: r.URL.RawQuery, // 正确编码查询参数
- }
- log.Printf("Forwarding request to %s", targetURL)
-
- // 执行重定向
- http.Redirect(w, r, targetURL.String(), http.StatusTemporaryRedirect)
- /*
-
-
-
- // 保留原请求的路径和查询字符串,构建完整的目标URL
- // 如果没有子路径,直接使用基础URL
- // 如果有子路径,将其附加到基础URL
-
- client := http.DefaultClient
- req, err := http.NewRequest(r.Method, targetURL.String(), r.Body)
- if err != nil {
- respondWithError(w, http.StatusBadRequest, "Failed to create request: "+err.Error())
- return
- }
-
- copyHeader(req.Header, r.Header)
-
- resp, err := client.Do(req)
- if err != nil {
- respondWithError(w, http.StatusServiceUnavailable, "Failed to forward request: "+err.Error())
- return
- }
- defer resp.Body.Close()
-
- // 将响应头复制到w.Header()
- for k, vv := range resp.Header {
- for _, v := range vv {
- w.Header().Add(k, v)
- }
- }
-
- w.WriteHeader(resp.StatusCode)
- if _, err := io.Copy(w, resp.Body); err != nil {
- log.Printf("Error copying response body: %v", err)
- }*/
-}
-
-// 复制源Header到目标Header
-// func copyHeader(dst, src http.Header) {
-// for k, vv := range src {
-// for _, v := range vv {
-// dst.Add(k, v)
-// }
-// }
-// }
diff --git a/godo/progress/linux.go b/godo/progress/linux.go
index c8d93db..a755018 100644
--- a/godo/progress/linux.go
+++ b/godo/progress/linux.go
@@ -7,6 +7,6 @@ import (
"os/exec"
)
-func setHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
+func SetHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
return cmd
}
diff --git a/godo/progress/map.go b/godo/progress/map.go
index d9c6ab9..de8303e 100644
--- a/godo/progress/map.go
+++ b/godo/progress/map.go
@@ -5,15 +5,4 @@ type ProcessInfo struct {
PingPath string // 增加ping或健康检查路径
}
-var ProcessInfoMap = map[string]ProcessInfo{
- "model": {Port: "56711", PingPath: "ping"},
- "goconv": {Port: "56712", PingPath: "ping"},
- "file": {Port: "56713", PingPath: "ping"},
- "knowledge": {Port: "56714", PingPath: "ping"},
- //"llmcpu": {Port: "56715", PingPath: "healthz"},
- "ollama": {Port: "56715", PingPath: ""},
- "sd": {Port: "56716", PingPath: "ping"},
- "voice": {Port: "56717", PingPath: "ping"},
- "localchat": {Port: "56718", PingPath: "ping"},
- "llmgpu": {Port: "56719", PingPath: "healthz"},
-}
+var ProcessInfoMap = map[string]ProcessInfo{}
diff --git a/godo/progress/progress.go b/godo/progress/progress.go
index 55232aa..3a51357 100644
--- a/godo/progress/progress.go
+++ b/godo/progress/progress.go
@@ -14,7 +14,7 @@ type Process struct {
Running bool
ExitCode int
Cmd *exec.Cmd
- PingURL string // 新增字段,存储每个进程的/ping URL
+ Waiting bool
LastPing time.Time
}
@@ -26,16 +26,13 @@ var (
func respondWithError(w http.ResponseWriter, code int, message string) {
http.Error(w, message, code)
}
-func registerProcess(name, pingURL string, cmdstr *exec.Cmd) {
+func RegisterProcess(name string, cmdstr *exec.Cmd) {
processesMu.Lock()
defer processesMu.Unlock()
- log.Printf("pingurl is: %s", pingURL) // 更改这里的格式化字符串
-
processes[name] = &Process{
Name: name,
Running: true,
- PingURL: pingURL,
Cmd: cmdstr,
}
}
@@ -54,10 +51,10 @@ func Status(w http.ResponseWriter, r *http.Request) {
if cmd.Cmd.ProcessState != nil && cmd.Cmd.ProcessState.Exited() {
cmd.Running = false
// 进程已经退出
- ps = append(ps, Process{Name: name, Running: false, PingURL: cmd.PingURL, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
+ ps = append(ps, Process{Name: name, Running: false, Waiting: cmd.Waiting, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
} else {
// 进程仍在运行
- ps = append(ps, Process{Name: name, Running: true, PingURL: cmd.PingURL, LastPing: cmd.LastPing})
+ ps = append(ps, Process{Name: name, Running: true, Waiting: cmd.Waiting, LastPing: cmd.LastPing})
}
}
diff --git a/godo/progress/start.go b/godo/progress/start.go
index 7585335..95da10e 100644
--- a/godo/progress/start.go
+++ b/godo/progress/start.go
@@ -2,17 +2,9 @@ package progress
import (
"fmt"
- "godo/libs"
- "log"
"net/http"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strings"
"github.com/gorilla/mux"
- "github.com/pkg/errors"
)
func StartProcess(w http.ResponseWriter, r *http.Request) {
@@ -20,17 +12,6 @@ func StartProcess(w http.ResponseWriter, r *http.Request) {
name := vars["name"]
processesMu.Lock()
defer processesMu.Unlock()
- cmd, ok := processes[name]
- if ok && !cmd.Running {
- err := cmd.Cmd.Start()
- if err != nil {
- respondWithError(w, http.StatusInternalServerError, err.Error())
- fmt.Fprintf(w, "failed to start process %s: %v", name, err)
- } else {
- w.WriteHeader(http.StatusOK)
- }
- return
- }
err := ExecuteScript(name)
if err != nil {
@@ -42,27 +23,15 @@ func StartProcess(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Process %s started.", name)
}
func ExecuteStartAll() error {
- log.Println("Starting all processes...")
+ processesMu.Lock()
+ defer processesMu.Unlock()
- userExeDir := libs.GetRunDir()
-
- userFis, err := os.ReadDir(userExeDir)
- if err != nil {
- return fmt.Errorf("failed to read user scripts directory: %w", err)
- }
-
- var names []string
- for _, userFi := range userFis {
- if userFi.IsDir() && !strings.HasPrefix(userFi.Name(), ".") {
- names = append(names, userFi.Name())
+ for name, cmd := range processes {
+ if err := cmd.Cmd.Start(); err != nil {
+ return fmt.Errorf("failed to stop process %s: %v", name, err)
}
}
- err = batchExecuteScript(names)
- if err != nil {
- return fmt.Errorf("failed to start processes: %w", err)
- }
-
return nil
}
func StartAll(w http.ResponseWriter, r *http.Request) {
@@ -74,19 +43,6 @@ func StartAll(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "All processes started.")
}
-func batchExecuteScript(names []string) error {
- for _, name := range names {
- // 检查processes中是否存在相同的name
- if _, exists := processes[name]; exists {
- log.Printf("Process with name %s already exists", name)
- continue
- }
- if err := ExecuteScript(name); err != nil {
- return errors.Wrapf(err, "failed to script: %v", err)
- }
- }
- return nil
-}
// ExecuteScript 执行指定名称的脚本。
// 参数:
@@ -94,51 +50,50 @@ func batchExecuteScript(names []string) error {
// 返回值:
// 返回可能遇到的错误,如果执行成功,则返回nil。
func ExecuteScript(name string) error {
- // 确保name存在于ProcessInfoMap中
- info, ok := ProcessInfoMap[name]
- if !ok {
+ cmd, exists := processes[name]
+ if !exists {
return fmt.Errorf("process information for '%s' not found", name)
}
- // 根据操作系统添加.exe后缀
- binaryExt := ""
- if runtime.GOOS == "windows" {
- binaryExt = ".exe"
+ if cmd.Running {
+ return fmt.Errorf("process %s is already running", name)
}
- scriptName := name + binaryExt
- exeDir := libs.GetRunDir() // 获取执行程序的目录
- scriptPath := filepath.Join(exeDir, name, scriptName) // 拼接脚本的完整路径
- // 检查脚本文件的存在性
- if !PathExists(scriptPath) {
- return fmt.Errorf("script file %s does not exist", scriptPath)
+ if err := cmd.Cmd.Start(); err != nil {
+ return fmt.Errorf("failed to start process %s: %v", name, err)
}
-
- // 设置并启动脚本执行命令
- var cmd *exec.Cmd
- 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
- }
- pingURL := fmt.Sprintf("http://localhost:%s/%s", info.Port, info.PingPath)
- registerProcess(name, pingURL, cmd)
- }()
+ cmd.Running = true
return nil
-}
-func PathExists(dir string) bool {
- _, err := os.Stat(dir)
- if err == nil {
- return true
- } else if os.IsNotExist(err) {
- return false
- } else if os.IsExist(err) {
- return true
- } else {
- return false
- }
+ // 确保name存在于ProcessInfoMap中
+ // _, ok := ProcessInfoMap[name]
+ // if !ok {
+ // return fmt.Errorf("process information for '%s' not found", name)
+ // }
+ // // 根据操作系统添加.exe后缀
+ // binaryExt := ""
+ // if runtime.GOOS == "windows" {
+ // binaryExt = ".exe"
+ // }
+ // scriptName := name + binaryExt
+ // exeDir := libs.GetRunDir() // 获取执行程序的目录
+ // scriptPath := filepath.Join(exeDir, name, scriptName) // 拼接脚本的完整路径
+ // // 检查脚本文件的存在性
+ // if !libs.PathExists(scriptPath) {
+ // return fmt.Errorf("script file %s does not exist", scriptPath)
+ // }
+
+ // // 设置并启动脚本执行命令
+ // var cmd *exec.Cmd
+ // 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)
+ // }()
+ // return nil
}
diff --git a/godo/progress/stop.go b/godo/progress/stop.go
index f6c408b..08a89ed 100644
--- a/godo/progress/stop.go
+++ b/godo/progress/stop.go
@@ -8,23 +8,27 @@ import (
"github.com/gorilla/mux"
)
+func StopCmd(name string) error {
+ cmd, ok := processes[name]
+ if !ok {
+ return fmt.Errorf("Process not found")
+ }
+ if err := cmd.Cmd.Process.Kill(); err != nil {
+ return fmt.Errorf("failed to kill process: %v", err)
+ }
+ //delete(processes, name) // 更新status,表示进程已停止
+ cmd.Running = false
+ return nil
+}
func StopProcess(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
- cmd, ok := processes[name]
- if !ok {
- respondWithError(w, http.StatusNotFound, "Process not found")
- return
- }
-
- // 停止进程并更新status
- if err := cmd.Cmd.Process.Kill(); err != nil {
+ err := StopCmd(name)
+ if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
- //delete(processes, name) // 更新status,表示进程已停止
- cmd.Running = false
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Process %s stopped.", name)
}
@@ -34,7 +38,7 @@ func StopAllHandler() error {
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 fmt.Errorf("failed to stop process %s: %v", name, err)
}
}
return nil
diff --git a/godo/progress/windows.go b/godo/progress/windows.go
index 2800d1c..b28192e 100644
--- a/godo/progress/windows.go
+++ b/godo/progress/windows.go
@@ -9,7 +9,7 @@ import (
"syscall"
)
-func setHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
+func SetHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
return cmd
}
diff --git a/godo/store/download.go b/godo/store/download.go
index d3a6544..fdee884 100644
--- a/godo/store/download.go
+++ b/godo/store/download.go
@@ -1,159 +1,156 @@
package store
import (
- "context"
"encoding/json"
+ "godo/files"
"godo/libs"
"io"
"log"
"net/http"
+ "os"
"path/filepath"
- "sync"
"time"
-
- "github.com/cavaliergopher/grab/v3"
)
+type ProgressReader struct {
+ reader io.Reader
+ total int64
+ err error
+}
+
type DownloadStatus struct {
- resp *grab.Response
- cancel context.CancelFunc
Name string `json:"name"`
Path string `json:"path"`
Url string `json:"url"`
Current int64 `json:"current"`
Size int64 `json:"size"`
Speed float64 `json:"speed"`
- Progress float64 `json:"progress"`
+ Progress int `json:"progress"`
Downloading bool `json:"downloading"`
Done bool `json:"done"`
}
-const (
- concurrency = 6 // 并发下载数
-)
-
-// var downloads = make(map[string]*grab.Response)
-var downloadsMutex sync.Mutex
-var downloadList map[string]*DownloadStatus
-
-func existsInDownloadList(url string) bool {
- _, ok := downloadList[url]
- return ok
+func (pr *ProgressReader) Read(p []byte) (n int, err error) {
+ n, err = pr.reader.Read(p)
+ pr.err = err
+ pr.total += int64(n)
+ return
}
-func PauseDownload(url string) {
- downloadsMutex.Lock()
- defer downloadsMutex.Unlock()
- ds, ok := downloadList[url]
- if ds.Url == url && ok {
- if ds.cancel != nil {
- ds.cancel()
- }
- ds.resp = nil
- ds.Downloading = false
- ds.Speed = 0
- }
-
-}
-
-func ContinueDownload(url string) {
- ds, ok := downloadList[url]
- if ds.Url == url && ok {
- if !ds.Downloading && ds.resp == nil && !ds.Done {
- ds.Downloading = true
-
- req, err := grab.NewRequest(ds.Path, ds.Url)
- if err != nil {
- ds.Downloading = false
- return
- }
- ctx, cancel := context.WithCancel(context.Background())
- ds.cancel = cancel
- req = req.WithContext(ctx)
- client := grab.NewClient()
- client.HTTPClient = &http.Client{
- Transport: &http.Transport{
- MaxIdleConnsPerHost: concurrency, // 设置并发连接数
- },
- }
- //resp := grab.DefaultClient.Do(req)
- resp := client.Do(req)
-
- if resp != nil && resp.HTTPResponse != nil &&
- resp.HTTPResponse.StatusCode >= 200 && resp.HTTPResponse.StatusCode < 300 {
- ds.resp = resp
- } else {
- ds.Downloading = false
- }
- }
- }
-
-}
-
-func Download(url string) {
- chacheDir := libs.GetCacheDir()
- absPath := filepath.Join(chacheDir, filepath.Base(url))
- if !existsInDownloadList(url) {
- downloadList[url] = &DownloadStatus{
- resp: nil,
- Name: filepath.Base(url),
- Path: absPath,
- Url: url,
- Downloading: false,
- }
- }
- ContinueDownload(url)
-}
-func GetDownload(url string) *DownloadStatus {
- downloadsMutex.Lock()
- defer downloadsMutex.Unlock()
- ds, ok := downloadList[url]
- if ds.resp != nil && ok {
- ds.Current = ds.resp.BytesComplete()
- ds.Size = ds.resp.Size()
- ds.Speed = ds.resp.BytesPerSecond()
- ds.Progress = 100 * ds.resp.Progress()
- ds.Downloading = !ds.resp.IsComplete()
- ds.Done = ds.resp.Progress() == 1
- if !ds.Downloading {
- ds.resp = nil
- }
- }
- if ds.Done {
- delete(downloadList, url)
- }
- return ds
-}
func DownloadHandler(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
- Download(url)
- ticker := time.NewTicker(500 * time.Millisecond)
+ log.Printf("Download url: %s", url)
+
+ // 获取下载目录,这里假设从请求参数中获取,如果没有则使用默认值
+ downloadDir := libs.GetCacheDir()
+ if downloadDir == "" {
+ downloadDir = "./downloads"
+ }
+
+ // 拼接完整的文件路径
+ fileName := filepath.Base(url)
+ filePath := filepath.Join(downloadDir, fileName)
+
+ // 开始下载
+ resp, err := http.Get(url)
+ if err != nil {
+ log.Printf("Failed to get file: %v", err)
+ http.Error(w, "Failed to get file", http.StatusInternalServerError)
+ return
+ }
+ defer resp.Body.Close()
+
+ // 检查文件是否已存在且大小一致
+ if fileInfo, err := os.Stat(filePath); err == nil {
+ if fileInfo.Size() == resp.ContentLength {
+ // 文件已存在且大小一致,无需下载
+ runDir := libs.GetRunDir()
+ err := files.HandlerFile(filePath, runDir)
+ if err != nil {
+ log.Printf("Error moving file: %v", err)
+ }
+ libs.SuccessMsg(w, "success", "File already exists and is of correct size")
+ return
+ } else {
+ // 重新打开响应体以便后续读取
+ resp.Body = http.NoBody
+ resp, err = http.Get(url)
+ if err != nil {
+ log.Printf("Failed to get file: %v", err)
+ http.Error(w, "Failed to get file", http.StatusInternalServerError)
+ return
+ }
+ }
+ }
+
+ // 创建文件
+ file, err := os.Create(filePath)
+ if err != nil {
+ log.Printf("Failed to create file: %v", err)
+ http.Error(w, "Failed to create file", http.StatusInternalServerError)
+ return
+ }
+ defer file.Close()
+
+ // 使用ProgressReader来跟踪进度
+ pr := &ProgressReader{reader: resp.Body}
+
+ // 启动定时器来报告进度
+ ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()
+
flusher, ok := w.(http.Flusher)
if !ok {
log.Printf("Streaming unsupported")
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
+
go func() {
for {
<-ticker.C
- ds := GetDownload(url)
- jsonBytes, err := json.Marshal(ds)
- if err != nil {
- log.Printf("Error marshaling FileProgress to JSON: %v", err)
- continue
+ rp := &DownloadStatus{
+ Name: fileName,
+ Path: filePath,
+ Url: url,
+ Current: pr.total,
+ Size: resp.ContentLength,
+ Speed: 0, // 这里可以计算速度,但为了简化示例,我们暂时设为0
+ Progress: int(100 * (float64(pr.total) / float64(resp.ContentLength))),
+ Downloading: pr.err == nil && pr.total < resp.ContentLength,
+ Done: pr.total == resp.ContentLength,
+ }
+ if pr.err != nil || rp.Done {
+ rp.Downloading = false
+ //log.Printf("Download complete: %s", filePath)
+ runDir := libs.GetRunDir()
+ err := files.HandlerFile(filePath, runDir)
+ if err != nil {
+ log.Printf("Error moving file: %v", err)
+ }
+ break
}
if w != nil {
- io.WriteString(w, string(jsonBytes))
+ jsonBytes, err := json.Marshal(rp)
+ if err != nil {
+ log.Printf("Error marshaling DownloadStatus to JSON: %v", err)
+ continue
+ }
+ w.Write(jsonBytes)
w.Write([]byte("\n"))
flusher.Flush()
} else {
log.Println("ResponseWriter is nil, cannot send progress")
}
- if ds.Done {
- return
- }
}
}()
+
+ // 将响应体的内容写入文件
+ _, err = io.Copy(file, pr)
+ if err != nil {
+ log.Printf("Failed to write file: %v", err)
+ http.Error(w, "Failed to write file", http.StatusInternalServerError)
+ return
+ }
+ libs.SuccessMsg(w, "success", "Download complete")
}
diff --git a/godo/store/install.go b/godo/store/install.go
new file mode 100644
index 0000000..e74a2d2
--- /dev/null
+++ b/godo/store/install.go
@@ -0,0 +1,401 @@
+package store
+
+import (
+ "encoding/json"
+ "fmt"
+ "godo/files"
+ "godo/libs"
+ "godo/progress"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+)
+
+type StoreInfo struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ NeedDownload bool `json:"needDownload"`
+ Icon string `json:"icon"`
+ Setting Setting `json:"setting"`
+ Config map[string]any `json:"config"`
+ Cmds map[string][]Cmd `json:"cmds"`
+ Install Install `json:"install"`
+ Start Install `json:"start"`
+}
+type Setting struct {
+ BinPath string `json:"binPath"`
+ ConfPath string `json:"confPath"`
+ DataDir string `json:"dataDir"`
+ LogDir string `json:"logDir"`
+}
+
+type Item struct {
+ Name string `json:"name"`
+ Value any `json:"value"`
+}
+type Cmd struct {
+ Name string `json:"name"`
+ FilePath string `json:"filePath,omitempty"`
+ Content string `json:"content,omitempty"`
+ BinPath string `json:"binPath,omitempty"`
+ Cmds []string `json:"cmds,omitempty"`
+ Waiting bool `json:"waiting"`
+ Envs []Item `json:"envs"`
+}
+
+type Install struct {
+ Envs []Item `json:"envs"`
+ Cmds []string `json:"cmds"`
+}
+
+func InstallHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Query().Get("name")
+ if pluginName == "" {
+ libs.ErrorMsg(w, "the app name is empty!")
+ return
+ }
+ exeDir := libs.GetRunDir()
+ exePath := filepath.Join(exeDir, pluginName)
+ //log.Printf("the app path is %s", exePath)
+ if !libs.PathExists(exePath) {
+ libs.ErrorMsg(w, "the app path is not exists!")
+ return
+ }
+ installFile := filepath.Join(exePath, "install.json")
+ if !libs.PathExists(installFile) {
+ libs.ErrorMsg(w, "the install.json is not exists!")
+ return
+ }
+ var storeInfo StoreInfo
+ content, err := os.ReadFile(installFile)
+ if err != nil {
+ libs.ErrorMsg(w, "cant read the install.json!")
+ return
+ }
+ //设置 info.json
+ exePath = strings.ReplaceAll(exePath, "\\", "/")
+ contentBytes := []byte(strings.ReplaceAll(string(content), "{exePath}", exePath))
+ //log.Printf("====content: %s", string(contentBytes))
+ err = json.Unmarshal(contentBytes, &storeInfo)
+ if err != nil {
+ log.Printf("Error during unmarshal: %v", err)
+ libs.ErrorMsg(w, "the install.json is error: "+err.Error())
+ return
+ }
+ replacePlaceholdersInCmds(&storeInfo)
+ infoFile := filepath.Join(exePath, "info.json")
+ err = SaveStoreInfo(storeInfo, infoFile)
+ if err != nil {
+ libs.ErrorMsg(w, "save the info.json is error!")
+ return
+ }
+
+ //设置config
+ if libs.PathExists(storeInfo.Setting.ConfPath + ".tpl") {
+ err = SaveStoreConfig(storeInfo, exePath)
+ if err != nil {
+ libs.ErrorMsg(w, "save the config is error!")
+ return
+ }
+ }
+ err = InstallStore(storeInfo)
+ if err != nil {
+ libs.ErrorMsg(w, "install the app is error!")
+ return
+ }
+ //复制static目录
+ staticPath := filepath.Join(exePath, "static")
+ if libs.PathExists(staticPath) {
+ staticDir := libs.GetStaticDir()
+ targetPath := filepath.Join(staticDir, pluginName)
+ err = os.Rename(staticPath, targetPath)
+ if err != nil {
+ libs.ErrorMsg(w, "copy the static is error!")
+ return
+ }
+ }
+ libs.SuccessMsg(w, "success", "install the app success!")
+}
+func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Query().Get("name")
+ if pluginName == "" {
+ libs.ErrorMsg(w, "the app name is empty!")
+ return
+ }
+ err := progress.StopCmd(pluginName)
+ if err != nil {
+ libs.ErrorMsg(w, "stop the app is error!")
+ return
+ }
+ exeDir := libs.GetRunDir()
+ exePath := filepath.Join(exeDir, pluginName)
+ //log.Printf("the app path is %s", exePath)
+ if libs.PathExists(exePath) {
+ err := os.RemoveAll(exePath)
+ if err != nil {
+ libs.ErrorMsg(w, "delete the app is error!")
+ return
+ }
+ }
+ staticDir := libs.GetStaticDir()
+ staticPath := filepath.Join(staticDir, pluginName)
+ if libs.PathExists(staticPath) {
+ err := os.RemoveAll(staticPath)
+ if err != nil {
+ libs.ErrorMsg(w, "delete the static is error!")
+ return
+ }
+ }
+ libs.SuccessMsg(w, "success", "uninstall the app success!")
+
+}
+func convertValueToString(value any) string {
+ var strValue string
+ switch v := value.(type) {
+ case string:
+ strValue = v
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
+ strValue = fmt.Sprintf("%v", v)
+ default:
+ strValue = fmt.Sprintf("%v", v) // 处理其他类型,例如布尔型或更复杂的类型
+ }
+ return strValue
+}
+
+func InstallStore(storeInfo StoreInfo) error {
+ err := SetEnvs(storeInfo.Install.Envs)
+ if err != nil {
+ return fmt.Errorf("failed to set install environment variable %s: %w", storeInfo.Name, err)
+ }
+ if len(storeInfo.Install.Cmds) > 0 {
+ for _, cmdKey := range storeInfo.Install.Cmds {
+ if _, ok := storeInfo.Cmds[cmdKey]; ok {
+ // 如果命令存在,你可以进一步处理 cmds
+ RunCmds(storeInfo, cmdKey)
+ }
+ }
+
+ }
+ return nil
+}
+func RunCmds(storeInfo StoreInfo, cmdKey string) error {
+ cmds := storeInfo.Cmds[cmdKey]
+ for _, cmd := range cmds {
+
+ if cmd.Name == "stop" {
+ runStop(storeInfo)
+ }
+ if cmd.Name == "start" {
+ runStart(storeInfo)
+ }
+ if cmd.Name == "restart" {
+ runRestart(storeInfo)
+ }
+ if cmd.Name == "exec" {
+ runExec(storeInfo, cmd)
+ }
+ if cmd.Name == "writeFile" {
+ err := WriteFile(cmd)
+ if err != nil {
+ return fmt.Errorf("failed to write to file: %w", err)
+ }
+ }
+ if cmd.Name == "deleteFile" {
+ err := DeleteFile(cmd)
+ if err != nil {
+ return fmt.Errorf("failed to delete file: %w", err)
+ }
+ }
+ if cmd.Name == "unzip" {
+ err := Unzip(cmd)
+ if err != nil {
+ return fmt.Errorf("failed to unzip file: %w", err)
+ }
+ }
+ if cmd.Name == "zip" {
+ err := Zip(cmd)
+ if err != nil {
+ return fmt.Errorf("failed to unzip file: %w", err)
+ }
+ }
+ if cmd.Name == "mkdir" {
+ err := MkDir(cmd)
+ if err != nil {
+ return fmt.Errorf("failed to create directory: %w", err)
+ }
+ }
+ }
+ return nil
+}
+func runStop(storeInfo StoreInfo) error {
+ return progress.StopCmd(storeInfo.Name)
+}
+func runStart(storeInfo StoreInfo) error {
+ err := SetEnvs(storeInfo.Start.Envs)
+ if err != nil {
+ return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
+ }
+ if len(storeInfo.Start.Cmds) > 0 {
+ if !libs.PathExists(storeInfo.Setting.BinPath) {
+ return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath)
+ }
+ cmd := exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...)
+ if runtime.GOOS == "windows" {
+ // 在Windows上,通过设置CreationFlags来隐藏窗口
+ cmd = progress.SetHideConsoleCursor(cmd)
+ }
+ // 启动脚本命令并返回可能的错误
+ if err := cmd.Start(); err != nil {
+ return fmt.Errorf("failed to start process %s: %w", storeInfo.Name, err)
+ }
+
+ progress.RegisterProcess(storeInfo.Name, cmd)
+ }
+ return nil
+}
+func runRestart(storeInfo StoreInfo) error {
+ err := runStop(storeInfo)
+ if err != nil {
+ return fmt.Errorf("failed to stop process %s: %w", storeInfo.Name, err)
+ }
+ return runStart(storeInfo)
+}
+func runExec(storeInfo StoreInfo, cmdParam Cmd) error {
+ err := SetEnvs(cmdParam.Envs)
+ if err != nil {
+ return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
+ }
+ log.Printf("bin path:%v", cmdParam.BinPath)
+ log.Printf("cmds:%v", cmdParam.Cmds)
+ cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...)
+ if runtime.GOOS == "windows" {
+ // 在Windows上,通过设置CreationFlags来隐藏窗口
+ cmd = progress.SetHideConsoleCursor(cmd)
+ }
+ if err := cmd.Start(); err != nil {
+ return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err)
+ }
+ if cmdParam.Waiting {
+ if err = cmd.Wait(); err != nil {
+ return fmt.Errorf("failed to wait for exec process %s: %w", storeInfo.Name, err)
+ }
+ }
+ log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name)
+ return nil
+}
+func WriteFile(cmd Cmd) error {
+ if cmd.FilePath != "" {
+ content := cmd.Content
+ if content != "" {
+ err := os.WriteFile(cmd.FilePath, []byte(content), 0644)
+ if err != nil {
+ return fmt.Errorf("failed to write to file: %w", err)
+ }
+ }
+ }
+ return nil
+}
+func DeleteFile(cmd Cmd) error {
+ if cmd.FilePath != "" {
+ err := os.Remove(cmd.FilePath)
+ if err != nil {
+ return fmt.Errorf("failed to delete file: %w", err)
+ }
+ }
+ return nil
+}
+func MkDir(cmd Cmd) error {
+ err := os.MkdirAll(cmd.FilePath, os.ModePerm)
+ if err != nil {
+ return fmt.Errorf("failed to make dir: %w", err)
+ }
+ return nil
+}
+func Unzip(cmd Cmd) error {
+ return files.Decompress(cmd.FilePath, cmd.Content)
+}
+func Zip(cmd Cmd) error {
+ return files.Encompress(cmd.FilePath, cmd.Content)
+}
+func SetEnvs(envs []Item) error {
+ if len(envs) > 0 {
+ for _, item := range envs {
+ strValue := convertValueToString(item.Value)
+ if strValue != "" {
+ err := os.Setenv(item.Name, strValue)
+ if err != nil {
+ return fmt.Errorf("failed to set environment variable %s: %w", item.Name, err)
+ }
+ }
+ }
+ }
+ return nil
+}
+func SaveStoreInfo(storeInfo StoreInfo, infoFile string) error {
+ // 使用 json.MarshalIndent 直接获取内容的字节切片
+ content, err := json.MarshalIndent(storeInfo, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to marshal reqBodies to JSON: %w", err)
+ }
+ if err := os.WriteFile(infoFile, content, 0644); err != nil {
+ return fmt.Errorf("failed to write to file: %w", err)
+ }
+ return nil
+}
+func SaveStoreConfig(storeInfo StoreInfo, exePath string) error {
+ content, err := os.ReadFile(storeInfo.Setting.ConfPath + ".tpl")
+ if err != nil {
+ return fmt.Errorf("failed to read tpl file: %w", err)
+ }
+ contentstr := strings.ReplaceAll(string(content), "{exePath}", exePath)
+ contentstr = ChangeConfig(storeInfo, contentstr)
+ if err := os.WriteFile(storeInfo.Setting.ConfPath, []byte(contentstr), 0644); err != nil {
+ return fmt.Errorf("failed to write to file: %w", err)
+ }
+ return nil
+}
+func ChangeConfig(storeInfo StoreInfo, contentstr string) string {
+ pattern := regexp.MustCompile(`\{(\w+)\}`)
+ for key, value := range storeInfo.Config {
+ strValue := convertValueToString(value)
+ contentstr = pattern.ReplaceAllStringFunc(contentstr, func(match string) string {
+ if strings.Trim(match, "{}") == key {
+ return strValue
+ }
+ return match
+ })
+ }
+ return contentstr
+}
+
+func replacePlaceholdersInCmds(storeInfo *StoreInfo) {
+ // 创建一个正则表达式模式,用于匹配形如{key}的占位符
+ pattern := regexp.MustCompile(`\{(\w+)\}`)
+
+ // 遍历Config中的所有键值对
+ for key, value := range storeInfo.Config {
+ // 遍历Cmds中的所有命令组
+ for cmdGroupName, cmds := range storeInfo.Cmds {
+ // 遍历每个命令组中的所有Cmd
+ for i, cmd := range cmds {
+ strValue := convertValueToString(value)
+ // 使用正则表达式替换Content中的占位符
+ storeInfo.Cmds[cmdGroupName][i].Content = pattern.ReplaceAllStringFunc(
+ cmd.Content,
+ func(match string) string {
+ // 检查占位符是否与Config中的键匹配
+ if strings.Trim(match, "{}") == key {
+ return strValue
+ }
+ return match
+ },
+ )
+ }
+ }
+ }
+}
diff --git a/godo/sys/store.go b/godo/store/store.go
similarity index 98%
rename from godo/sys/store.go
rename to godo/store/store.go
index 3529cb2..d0ae65a 100644
--- a/godo/sys/store.go
+++ b/godo/store/store.go
@@ -1,4 +1,4 @@
-package sys
+package store
import (
"encoding/json"
diff --git a/godo/sys/update.go b/godo/sys/update.go
index 60c6c19..fc7348a 100644
--- a/godo/sys/update.go
+++ b/godo/sys/update.go
@@ -61,7 +61,7 @@ func UpdateAppHandler(w http.ResponseWriter, r *http.Request) {
defer resp.Body.Close()
pr := &ProgressReader{reader: resp.Body}
- ticker := time.NewTicker(250 * time.Millisecond)
+ ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()
flusher, ok := w.(http.Flusher)
if !ok {