godoos/godo/store/install.go
2024-07-30 08:12:52 +08:00

417 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package store
import (
"encoding/json"
"fmt"
"godo/files"
"godo/libs"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
)
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 int `json:"waiting"`
Kill bool `json:"kill"`
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
log.Printf("copy the static is error! %v", err)
}
}
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
}
log.Printf("uninstall the app %s", pluginName)
err := StopCmd(pluginName)
if err != nil {
// libs.ErrorMsg(w, "stop the app is error!")
// return
log.Printf("stop the app is error! %s", err)
}
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 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 !libs.PathExists(storeInfo.Setting.BinPath) {
return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath)
}
var cmd *exec.Cmd
if len(storeInfo.Start.Cmds) > 0 {
cmd = exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...)
} else {
cmd = exec.Command(storeInfo.Setting.BinPath)
}
if runtime.GOOS == "windows" {
// 在Windows上通过设置CreationFlags来隐藏窗口
cmd = SetHideConsoleCursor(cmd)
}
// 启动脚本命令并返回可能的错误
go func(cmd *exec.Cmd) {
if err := cmd.Start(); err != nil {
log.Println("Error starting script:", err)
return
}
RegisterProcess(storeInfo.Name, cmd)
}(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 = SetHideConsoleCursor(cmd)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err)
}
if cmdParam.Waiting > 0 {
if err = cmd.Wait(); err != nil {
return fmt.Errorf("failed to wait for exec process %s: %w", storeInfo.Name, err)
}
time.Sleep(time.Second * time.Duration(cmdParam.Waiting))
}
if cmdParam.Kill {
if err := cmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to kill process: %v", 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
},
)
}
}
}
}