diff --git a/agent/app/api/v2/nginx.go b/agent/app/api/v2/nginx.go index 7244becec498..05d2a0999170 100644 --- a/agent/app/api/v2/nginx.go +++ b/agent/app/api/v2/nginx.go @@ -116,3 +116,60 @@ func (b *BaseApi) ClearNginxProxyCache(c *gin.Context) { } helper.SuccessWithOutData(c) } + +// @Tags OpenResty +// @Summary Build OpenResty +// @Description 构建 OpenResty +// @Accept json +// @Param request body request.NginxBuildReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /openresty/build [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"构建 OpenResty","formatEN":"Build OpenResty"} +func (b *BaseApi) BuildNginx(c *gin.Context) { + var req request.NginxBuildReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := nginxService.Build(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} + +// @Tags OpenResty +// @Summary Update OpenResty module +// @Description 更新 OpenResty 模块 +// @Accept json +// @Param request body request.NginxModuleUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /openresty/module/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 OpenResty 模块","formatEN":"Update OpenResty module"} +func (b *BaseApi) UpdateNginxModule(c *gin.Context) { + var req request.NginxModuleUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := nginxService.UpdateModule(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} + +// @Tags OpenResty +// @Summary Get OpenResty modules +// @Description 获取 OpenResty 模块 +// @Success 200 {array} response.NginxModule +// @Security ApiKeyAuth +// @Router /openresty/modules [get] +func (b *BaseApi) GetNginxModules(c *gin.Context) { + modules, err := nginxService.GetModules() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, modules) +} diff --git a/agent/app/dto/request/nginx.go b/agent/app/dto/request/nginx.go index 9405dec0c31b..37eaeaed1b04 100644 --- a/agent/app/dto/request/nginx.go +++ b/agent/app/dto/request/nginx.go @@ -106,3 +106,16 @@ type NginxRedirectUpdate struct { Content string `json:"content" validate:"required"` Name string `json:"name" validate:"required"` } + +type NginxBuildReq struct { + TaskID string `json:"taskID" validate:"required"` +} + +type NginxModuleUpdate struct { + Operate string `json:"operate" validate:"required,oneof=create delete update"` + Name string `json:"name" validate:"required"` + Script string `json:"script"` + Packages string `json:"packages"` + Enable bool `json:"enable"` + Params string `json:"params"` +} diff --git a/agent/app/dto/response/nginx.go b/agent/app/dto/response/nginx.go index 24e3f442bcca..415d7b26c5b1 100644 --- a/agent/app/dto/response/nginx.go +++ b/agent/app/dto/response/nginx.go @@ -67,3 +67,11 @@ type NginxProxyCache struct { CacheExpire int `json:"cacheExpire" ` CacheExpireUnit string `json:"cacheExpireUnit" ` } + +type NginxModule struct { + Name string `json:"name"` + Script string `json:"script"` + Packages []string `json:"packages"` + Params string `json:"params"` + Enable bool `json:"enable"` +} diff --git a/agent/app/service/nginx.go b/agent/app/service/nginx.go index e96c7b12a0ce..e80ad0e2edec 100644 --- a/agent/app/service/nginx.go +++ b/agent/app/service/nginx.go @@ -1,7 +1,13 @@ package service import ( + "bufio" + "encoding/json" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/subosito/gotenv" "io" "net/http" "os" @@ -29,6 +35,10 @@ type INginxService interface { GetStatus() (response.NginxStatus, error) UpdateConfigFile(req request.NginxConfigFileUpdate) error ClearProxyCache() error + + Build(req request.NginxBuildReq) error + GetModules() ([]response.NginxModule, error) + UpdateModule(req request.NginxModuleUpdate) error } func NewINginxService() INginxService { @@ -152,3 +162,157 @@ func (n NginxService) ClearProxyCache() error { } return nil } + +func (n NginxService) Build(req request.NginxBuildReq) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + fileOp := files.NewFileOp() + buildPath := path.Join(nginxInstall.GetPath(), "build") + if !fileOp.Stat(buildPath) { + return buserr.New("ErrBuildDirNotFound") + } + moduleConfigPath := path.Join(buildPath, "module.json") + moduleContent, err := fileOp.GetContent(moduleConfigPath) + if err != nil { + return err + } + var ( + modules []response.NginxModule + addModuleParams []string + addPackages []string + ) + if len(moduleContent) > 0 { + _ = json.Unmarshal(moduleContent, &modules) + bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return err + } + defer bashFile.Close() + bashFileWriter := bufio.NewWriter(bashFile) + for _, module := range modules { + if !module.Enable { + continue + } + _, err = bashFileWriter.WriteString(module.Script + "\n") + if err != nil { + return err + } + addModuleParams = append(addModuleParams, module.Params) + addPackages = append(addPackages, module.Packages...) + } + err = bashFileWriter.Flush() + if err != nil { + return err + } + } + envs, err := gotenv.Read(nginxInstall.GetEnvPath()) + if err != nil { + return err + } + envs["RESTY_CONFIG_OPTIONS_MORE"] = "" + envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = "" + if len(addModuleParams) > 0 { + envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ") + } + if len(addPackages) > 0 { + envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ") + } + _ = gotenv.Write(envs, nginxInstall.GetEnvPath()) + + buildTask, err := task.NewTaskWithOps(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp, req.TaskID, nginxInstall.ID) + if err != nil { + return err + } + buildTask.AddSubTask("", func(t *task.Task) error { + if err = cmd2.ExecWithLogFile(fmt.Sprintf("docker compose -f %s build", nginxInstall.GetComposePath()), 15*time.Minute, t.Task.LogFile); err != nil { + return err + } + _, err = compose.DownAndUp(nginxInstall.GetComposePath()) + return err + }, nil) + + go func() { + _ = buildTask.Execute() + }() + return nil +} + +func (n NginxService) GetModules() ([]response.NginxModule, error) { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + fileOp := files.NewFileOp() + var modules []response.NginxModule + moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json") + if !fileOp.Stat(moduleConfigPath) { + return modules, nil + } + moduleContent, err := fileOp.GetContent(moduleConfigPath) + if err != nil { + return nil, err + } + if len(moduleContent) > 0 { + _ = json.Unmarshal(moduleContent, &modules) + } + return modules, nil +} + +func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + fileOp := files.NewFileOp() + var modules []response.NginxModule + moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json") + if !fileOp.Stat(moduleConfigPath) { + _ = fileOp.CreateFile(moduleConfigPath) + } + moduleContent, err := fileOp.GetContent(moduleConfigPath) + if err != nil { + return err + } + if len(moduleContent) > 0 { + _ = json.Unmarshal(moduleContent, &modules) + } + switch req.Operate { + case "create": + for _, module := range modules { + if module.Name == req.Name { + return buserr.New("ErrNameIsExist") + } + } + modules = append(modules, response.NginxModule{ + Name: req.Name, + Script: req.Script, + Packages: strings.Split(req.Packages, " "), + Params: req.Params, + Enable: true, + }) + case "update": + for i, module := range modules { + if module.Name == req.Name { + modules[i].Script = req.Script + modules[i].Packages = strings.Split(req.Packages, " ") + modules[i].Params = req.Params + modules[i].Enable = req.Enable + break + } + } + case "delete": + for i, module := range modules { + if module.Name == req.Name { + modules = append(modules[:i], modules[i+1:]...) + break + } + } + } + moduleByte, err := json.Marshal(modules) + if err != nil { + return err + } + return fileOp.SaveFileWithByte(moduleConfigPath, moduleByte, 0644) +} diff --git a/agent/app/task/task.go b/agent/app/task/task.go index 75dec19092c9..cea2f77c8ead 100644 --- a/agent/app/task/task.go +++ b/agent/app/task/task.go @@ -51,6 +51,7 @@ const ( TaskRestart = "TaskRestart" TaskBackup = "TaskBackup" TaskSync = "TaskSync" + TaskBuild = "TaskBuild" ) const ( diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 6b618795b5f3..995c326ff013 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -99,6 +99,7 @@ ErrDomainIsUsed: "Domain is already used by website {{ .name }}" ErrDomainFormat: "{{ .name }} domain format error" ErrDefaultAlias: "default is a reserved code name, please use another code name" ErrParentWebsite: "You need to delete the subsite(s) {{ .name }} first" +ErrBuildDirNotFound: "Build directory does not exist" #ssl ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed" @@ -244,4 +245,5 @@ TaskSync: "Sync" LocalApp: "Local App" SubTask: "Subtask" RuntimeExtension: "Runtime Extension" +TaskBuild: "Build" diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 87c395e38e36..259d156df503 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -99,6 +99,7 @@ ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用" ErrDomainFormat: "{{ .name }} 域名格式不正確" ErrDefaultAlias: "default 為保留代號,請使用其他代號" ErrParentWebsite: "需要先刪除子網站 {{ .name }}" +ErrBuildDirNotFound: "編譯目錄不存在" #ssl ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除" @@ -246,4 +247,6 @@ TaskSync: "同步" LocalApp: "本地應用" SubTask: "子任務" RuntimeExtension: "運行環境擴展" +TaskBuild: "構建" + diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index bce76eaf71d3..7d5b63fc2049 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -99,6 +99,7 @@ ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用" ErrDomainFormat: "{{ .name }} 域名格式不正确" ErrDefaultAlias: "default 为保留代号,请使用其他代号" ErrParentWebsite: "需要先删除子网站 {{ .name }}" +ErrBuildDirNotFound: "构建目录不存在" #ssl ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除" @@ -247,4 +248,5 @@ AppStore: "应用商店" TaskSync: "同步" LocalApp: "本地应用" SubTask: "子任务" -RuntimeExtension: "运行环境扩展" \ No newline at end of file +RuntimeExtension: "运行环境扩展" +TaskBuild: "构建" \ No newline at end of file diff --git a/agent/router/ro_nginx.go b/agent/router/ro_nginx.go index f4f1ea772780..08b3ed21292e 100644 --- a/agent/router/ro_nginx.go +++ b/agent/router/ro_nginx.go @@ -19,5 +19,8 @@ func (a *NginxRouter) InitRouter(Router *gin.RouterGroup) { groupRouter.GET("/status", baseApi.GetNginxStatus) groupRouter.POST("/file", baseApi.UpdateNginxFile) groupRouter.POST("/clear", baseApi.ClearNginxProxyCache) + groupRouter.POST("/build", baseApi.BuildNginx) + groupRouter.POST("/modules/update", baseApi.UpdateNginxModule) + groupRouter.GET("/modules", baseApi.GetNginxModules) } } diff --git a/frontend/src/api/interface/nginx.ts b/frontend/src/api/interface/nginx.ts index dab9465bf0af..5f5b4b5d1d4c 100644 --- a/frontend/src/api/interface/nginx.ts +++ b/frontend/src/api/interface/nginx.ts @@ -28,4 +28,25 @@ export namespace Nginx { content: string; backup: boolean; } + + export interface NginxBuildReq { + taskID: string; + } + + export interface NginxModule { + name: string; + script?: string; + packages?: string[]; + enable: boolean; + params: string; + } + + export interface NginxModuleUpdate { + operate: string; + name: string; + script?: string; + packages?: string; + enable: boolean; + params: string; + } } diff --git a/frontend/src/api/modules/nginx.ts b/frontend/src/api/modules/nginx.ts index c36cd6775c5f..dd6061f91d34 100644 --- a/frontend/src/api/modules/nginx.ts +++ b/frontend/src/api/modules/nginx.ts @@ -11,7 +11,7 @@ export const GetNginxConfigByScope = (req: Nginx.NginxScopeReq) => { }; export const UpdateNginxConfigByScope = (req: Nginx.NginxConfigReq) => { - return http.post(`/openresty/update`, req); + return http.post(`/openresty/update`, req); }; export const GetNginxStatus = () => { @@ -19,9 +19,21 @@ export const GetNginxStatus = () => { }; export const UpdateNginxConfigFile = (req: Nginx.NginxFileUpdate) => { - return http.post(`/openresty/file`, req); + return http.post(`/openresty/file`, req); }; export const ClearNginxCache = () => { - return http.post(`/openresty/clear`); + return http.post(`/openresty/clear`); +}; + +export const BuildNginx = (req: Nginx.NginxBuildReq) => { + return http.post(`/openresty/build`, req); +}; + +export const GetNginxModules = () => { + return http.get(`/openresty/modules`); +}; + +export const UpdateNginxModule = (req: Nginx.NginxModuleUpdate) => { + return http.post(`/openresty/modules/update`, req); }; diff --git a/frontend/src/components/task-log/index.vue b/frontend/src/components/task-log/index.vue index 5df108a2d31f..2366256a2fad 100644 --- a/frontend/src/components/task-log/index.vue +++ b/frontend/src/components/task-log/index.vue @@ -9,13 +9,7 @@ :width="width" >
- +
@@ -98,7 +92,7 @@ const getContent = (pre: boolean) => { }); data.value = res.data; if (res.data.content != '') { - if (stopSignals.some((signal) => res.data.content.endsWith(signal))) { + if (stopSignals.some((signal) => res.data.content.includes(signal))) { onCloseLog(); } if (end.value) { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 9e82b12a3b46..6c02b91eaa80 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2251,6 +2251,15 @@ const message = { clearProxyCache: 'Clear reverse proxy cache', clearProxyCacheWarn: 'Clearing the reverse proxy cache will affect all websites configured with cache and requires restarting OpenResty. Do you want to continue? ', + create: 'Add a new module', + update: 'Edit a module', + params: 'Parameters', + packages: 'Packages', + script: 'Scripts', + module: 'Modules', + build: 'Build', + buildWarn: + 'Building OpenResty requires reserving a certain amount of CPU and memory, which may take a long time, please be patient', }, ssl: { create: 'Apply', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 62d65bf34505..83156de9e617 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -2096,6 +2096,14 @@ const message = { saveAndReload: '保存並重載', clearProxyCache: '清除反代快取', clearProxyCacheWarn: '清除反代快取會影響所有配置快取的網站,並且需要重新啟動 OpenResty, 是否繼續? ', + create: '新增模組', + update: '編輯模組', + params: '參數', + packages: '軟體包', + script: '腳本', + module: '模組', + build: '建構', + buildWarn: '建構 OpenResty 需要預留一定的 CPU 和內存,時間較長,請耐心等待', }, ssl: { create: '申請證書', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f19f53538540..cbe44afb644e 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2097,6 +2097,14 @@ const message = { saveAndReload: '保存并重载', clearProxyCache: '清除反代缓存', clearProxyCacheWarn: '清除反代缓存会影响所有配置缓存的网站,并且需要重启 OpenResty, 是否继续?', + create: '新增模块', + update: '编辑模块', + params: '参数', + packages: '软件包', + script: '脚本', + module: '模块', + build: '构建', + buildWarn: '构建 OpenResty 需要预留一定的 CPU 和内存,时间较长,请耐心等待', }, ssl: { create: '申请证书', diff --git a/frontend/src/views/log/task/index.vue b/frontend/src/views/log/task/index.vue index 14d586736066..a0125ee0e48b 100644 --- a/frontend/src/views/log/task/index.vue +++ b/frontend/src/views/log/task/index.vue @@ -23,6 +23,9 @@
{{ $t('commons.status.success') }}
+
+ {{ $t('process.running') }} +
{{ row.primaryDomain }} - + @@ -95,6 +100,9 @@ {{ getUrl(domain, row) }} + + + diff --git a/frontend/src/views/website/website/nginx/index.vue b/frontend/src/views/website/website/nginx/index.vue index 3ceff49930e5..a58b9adeef5c 100644 --- a/frontend/src/views/website/website/nginx/index.vue +++ b/frontend/src/views/website/website/nginx/index.vue @@ -23,12 +23,16 @@ > {{ $t('website.log') }} + + {{ $t('runtime.module') }} + @@ -39,6 +43,7 @@ import { nextTick, ref } from 'vue'; import ContainerLog from '@/components/container-log/index.vue'; import NginxPer from './performance/index.vue'; import Status from './status/index.vue'; +import Module from './module/index.vue'; const activeName = ref('1'); const dialogContainerLogRef = ref(); diff --git a/frontend/src/views/website/website/nginx/module/index.vue b/frontend/src/views/website/website/nginx/module/index.vue new file mode 100644 index 000000000000..21bc78ab04c0 --- /dev/null +++ b/frontend/src/views/website/website/nginx/module/index.vue @@ -0,0 +1,105 @@ + + diff --git a/frontend/src/views/website/website/nginx/module/operate/index.vue b/frontend/src/views/website/website/nginx/module/operate/index.vue new file mode 100644 index 000000000000..8ff3728a31e0 --- /dev/null +++ b/frontend/src/views/website/website/nginx/module/operate/index.vue @@ -0,0 +1,92 @@ + + +