diff --git a/backend/app/service/backup_runtime.go b/backend/app/service/backup_runtime.go new file mode 100644 index 000000000..bad66aa3b --- /dev/null +++ b/backend/app/service/backup_runtime.go @@ -0,0 +1,129 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/compose" + "github.com/1Panel-dev/1Panel/backend/utils/files" + "io/fs" + "os" + "path" + "strings" + "time" +) + +func handleRuntimeBackup(runtime *model.Runtime, backupDir, fileName string) error { + fileOp := files.NewFileOp() + tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) + if !fileOp.Stat(tmpDir) { + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) + } + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + remarkInfo, _ := json.Marshal(runtime) + remarkInfoPath := fmt.Sprintf("%s/runtime.json", tmpDir) + if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil { + return err + } + + appPath := runtime.GetPath() + if err := handleTar(appPath, tmpDir, "runtime.tar.gz", ""); err != nil { + return err + } + if err := handleTar(tmpDir, backupDir, fileName, ""); err != nil { + return err + } + return nil +} + +func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback bool) error { + isOk := false + fileOp := files.NewFileOp() + if err := handleUnTar(recoverFile, path.Dir(recoverFile)); err != nil { + return err + } + tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "") + defer func() { + _, _ = compose.Up(runtime.GetComposePath()) + _ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", "")) + }() + + if !fileOp.Stat(tmpPath+"/runtime.json") || !fileOp.Stat(tmpPath+"/runtime.tar.gz") { + return errors.New("the wrong recovery package does not have runtime.json or runtime.tar.gz files") + } + var oldRuntime model.Runtime + runtimeJson, err := os.ReadFile(tmpPath + "/runtime.json") + if err != nil { + return err + } + if err := json.Unmarshal(runtimeJson, &oldRuntime); err != nil { + return fmt.Errorf("unmarshal runtime.json failed, err: %v", err) + } + if oldRuntime.Type != runtime.Type || oldRuntime.Name != runtime.Name { + return errors.New("the current backup file does not match the application") + } + + if !isRollback { + rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("runtime/%s_%s.tar.gz", runtime.Name, time.Now().Format("20060102150405"))) + if err := handleRuntimeBackup(runtime, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil { + return fmt.Errorf("backup runtime %s for rollback before recover failed, err: %v", runtime.Name, err) + } + defer func() { + if !isOk { + global.LOG.Info("recover failed, start to rollback now") + if err := handleRuntimeRecover(runtime, rollbackFile, true); err != nil { + global.LOG.Errorf("rollback runtime %s from %s failed, err: %v", runtime.Name, rollbackFile, err) + return + } + global.LOG.Infof("rollback runtime %s from %s successful", runtime.Name, rollbackFile) + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + }() + } + + newEnvFile, err := coverEnvJsonToStr(runtime.Env) + if err != nil { + return err + } + runtimeDir := runtime.GetPath() + backPath := fmt.Sprintf("%s_bak", runtimeDir) + _ = fileOp.Rename(runtimeDir, backPath) + _ = fileOp.CreateDir(runtimeDir, 0755) + + if err := handleUnTar(tmpPath+"/runtime.tar.gz", fmt.Sprintf("%s/%s", constant.RuntimeDir, runtime.Type)); err != nil { + global.LOG.Errorf("handle recover from runtime.tar.gz failed, err: %v", err) + _ = fileOp.DeleteDir(runtimeDir) + _ = fileOp.Rename(backPath, runtimeDir) + return err + } + _ = fileOp.DeleteDir(backPath) + + if len(newEnvFile) != 0 { + envPath := fmt.Sprintf("%s/%s/%s/.env", constant.RuntimeDir, runtime.Type, runtime.Name) + file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + _, _ = file.WriteString(newEnvFile) + } + + oldRuntime.ID = runtime.ID + oldRuntime.Status = constant.RuntimeStarting + if err := runtimeRepo.Save(&oldRuntime); err != nil { + global.LOG.Errorf("save db app install failed, err: %v", err) + return err + } + isOk = true + return nil +} diff --git a/backend/app/service/backup_website.go b/backend/app/service/backup_website.go index 27d78c847..b96d797e2 100644 --- a/backend/app/service/backup_website.go +++ b/backend/app/service/backup_website.go @@ -132,7 +132,8 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback return err } - if website.Type == constant.Deployment { + switch website.Type { + case constant.Deployment: app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return err @@ -145,7 +146,19 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback global.LOG.Errorf("docker-compose restart failed, err: %v", err) return err } + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + if runtime.Type == constant.RuntimeNode { + if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true); err != nil { + return err + } + global.LOG.Info("put runtime.tar.gz into tmp dir successful") + } } + siteDir := fmt.Sprintf("%s/openresty/%s/www/sites", constant.AppInstallDir, nginxInfo.Name) if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), siteDir); err != nil { global.LOG.Errorf("handle recover from web.tar.gz failed, err: %v", err) @@ -194,7 +207,8 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err } global.LOG.Info("put openresty conf into tmp dir successful") - if website.Type == constant.Deployment { + switch website.Type { + case constant.Deployment: app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return err @@ -203,7 +217,19 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err return err } global.LOG.Info("put app.tar.gz into tmp dir successful") + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + if runtime.Type == constant.RuntimeNode { + if err := handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias)); err != nil { + return err + } + global.LOG.Info("put runtime.tar.gz into tmp dir successful") + } } + websiteDir := fmt.Sprintf("%s/openresty/%s/www/sites/%s", constant.AppInstallDir, nginxInfo.Name, website.Alias) if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), ""); err != nil { return err diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 992c59395..c0d32bf07 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -263,38 +263,44 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) return err } website.RuntimeID = runtime.ID - if runtime.Resource == constant.ResourceAppstore { - var ( - req request.AppInstallCreate - install *model.AppInstall - ) - reg, _ := regexp.Compile(`[^a-z0-9_-]+`) - req.Name = reg.ReplaceAllString(strings.ToLower(create.PrimaryDomain), "") - req.AppDetailId = create.AppInstall.AppDetailId - req.Params = create.AppInstall.Params - req.Params["IMAGE_NAME"] = runtime.Image - req.AppContainerConfig = create.AppInstall.AppContainerConfig - req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") - tx, installCtx := getTxAndContext() - install, err = NewIAppService().Install(installCtx, req) - if err != nil { - tx.Rollback() - return err + switch runtime.Type { + case constant.RuntimePHP: + if runtime.Resource == constant.ResourceAppstore { + var ( + req request.AppInstallCreate + install *model.AppInstall + ) + reg, _ := regexp.Compile(`[^a-z0-9_-]+`) + req.Name = reg.ReplaceAllString(strings.ToLower(create.PrimaryDomain), "") + req.AppDetailId = create.AppInstall.AppDetailId + req.Params = create.AppInstall.Params + req.Params["IMAGE_NAME"] = runtime.Image + req.AppContainerConfig = create.AppInstall.AppContainerConfig + req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") + tx, installCtx := getTxAndContext() + install, err = NewIAppService().Install(installCtx, req) + if err != nil { + tx.Rollback() + return err + } + tx.Commit() + website.AppInstallID = install.ID + appInstall = install + website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) + } else { + website.ProxyType = create.ProxyType + if website.ProxyType == constant.RuntimeProxyUnix { + proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock")) + } + if website.ProxyType == constant.RuntimeProxyTcp { + proxy = fmt.Sprintf("127.0.0.1:%d", create.Port) + } + website.Proxy = proxy } - tx.Commit() - website.AppInstallID = install.ID - appInstall = install - website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) - } else { - website.ProxyType = create.ProxyType - if website.ProxyType == constant.RuntimeProxyUnix { - proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock")) - } - if website.ProxyType == constant.RuntimeProxyTcp { - proxy = fmt.Sprintf("127.0.0.1:%d", create.Port) - } - website.Proxy = proxy + case constant.RuntimeNode: + website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port) } + } var domains []model.WebsiteDomain diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 3c5959260..fff846003 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -216,20 +216,22 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) server.UpdateDirective("include", []string{nginxInclude}) case constant.Runtime: - if runtime.Resource == constant.ResourceLocal { - switch runtime.Type { - case constant.RuntimePHP: - server.UpdateRoot(rootIndex) - localPath := path.Join(nginxInstall.GetPath(), rootIndex, "index.php") - server.UpdatePHPProxy([]string{website.Proxy}, localPath) - } - } - if runtime.Resource == constant.ResourceAppstore { - switch runtime.Type { - case constant.RuntimePHP: + switch runtime.Type { + case constant.RuntimePHP: + if runtime.Resource == constant.ResourceLocal { + switch runtime.Type { + case constant.RuntimePHP: + server.UpdateRoot(rootIndex) + localPath := path.Join(nginxInstall.GetPath(), rootIndex, "index.php") + server.UpdatePHPProxy([]string{website.Proxy}, localPath) + } + } else { server.UpdateRoot(rootIndex) server.UpdatePHPProxy([]string{website.Proxy}, "") } + case constant.RuntimeNode: + proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port) + server.UpdateRootProxy([]string{proxy}) } } diff --git a/frontend/src/views/website/website/config/index.vue b/frontend/src/views/website/website/config/index.vue index d090d1bca..97098d011 100644 --- a/frontend/src/views/website/website/config/index.vue +++ b/frontend/src/views/website/website/config/index.vue @@ -93,7 +93,7 @@ onMounted(() => { website.value = res.data; if (res.data.type === 'runtime') { const runRes = await GetRuntime(res.data.runtimeID); - if (runRes.data.resource === 'appstore') { + if (runRes.data.type == 'php' && runRes.data.resource === 'appstore') { configPHP.value = true; } } diff --git a/frontend/src/views/website/website/config/resource/index.vue b/frontend/src/views/website/website/config/resource/index.vue index 030bbd283..843c4e0c0 100644 --- a/frontend/src/views/website/website/config/resource/index.vue +++ b/frontend/src/views/website/website/config/resource/index.vue @@ -39,7 +39,7 @@ const getWebsiteDetail = async () => { if (res.data.type === 'runtime') { installId.value = res.data.appInstallId; const runRes = await GetRuntime(res.data.runtimeID); - if (runRes.data.resource === 'appstore') { + if (runRes.data.type == 'php' && runRes.data.resource === 'appstore') { configPHP.value = true; } } diff --git a/frontend/src/views/website/website/create/index.vue b/frontend/src/views/website/website/create/index.vue index 721972834..2bace8dfc 100644 --- a/frontend/src/views/website/website/create/index.vue +++ b/frontend/src/views/website/website/create/index.vue @@ -152,48 +152,70 @@
- - - - - - -
- - - - - - - - - + + + + + + + + + + + + + + + {{ run.name }} + + {{ ' [' + $t('runtime.' + run.resource) + ']' }} + + + + + + + +
+ +
+ + + + + + + + + +
@@ -363,6 +385,7 @@ const website = ref({ port: 9000, proxyProtocol: 'http://', proxyAddress: '', + runtimeType: 'php', }); const rules = ref({ primaryDomain: [Rules.domainWithPort], @@ -383,6 +406,7 @@ const rules = ref({ }, proxyType: [Rules.requiredSelect], port: [Rules.port], + runtimeType: [Rules.requiredInput], }); const open = ref(false); @@ -500,6 +524,16 @@ const getAppDetailByID = (id: number) => { }); }; +const changeRuntimeType = () => { + runtimeReq.value.type = website.value.runtimeType; + if (website.value.runtimeType == 'php') { + runtimeReq.value.status = 'normal'; + } else { + runtimeReq.value.status = 'running'; + } + getRuntimes(); +}; + const changeRuntime = (runID: number) => { runtimes.value.forEach((item) => { if (item.id === runID) {