From ee0aa532d7e693ee2141e9cb7130ef6acf814d07 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Thu, 20 Jun 2024 22:18:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=B3=E8=AF=B7=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E4=B9=8B=E5=90=8E=E6=94=AF=E6=8C=81=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E8=84=9A=E6=9C=AC=20(#5517)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs https://github.com/1Panel-dev/1Panel/issues/3197 --- backend/app/dto/request/website_ssl.go | 6 ++ backend/app/model/website_ssl.go | 2 + backend/app/service/website_ca.go | 17 +++++ backend/app/service/website_ssl.go | 31 ++++++++- backend/i18n/lang/zh.yaml | 3 + backend/init/migration/migrate.go | 1 + backend/init/migration/migrations/v_1_10.go | 10 +++ backend/utils/cmd/cmd.go | 21 ++++++ frontend/src/api/interface/website.ts | 2 + frontend/src/components/log-file/index.vue | 4 +- frontend/src/lang/modules/en.ts | 4 ++ frontend/src/lang/modules/tw.ts | 4 ++ frontend/src/lang/modules/zh.ts | 4 ++ .../src/views/website/ssl/ca/obtain/index.vue | 12 ++++ .../src/views/website/ssl/create/index.vue | 68 ++++++++++++------- frontend/src/views/website/ssl/index.vue | 2 +- 16 files changed, 162 insertions(+), 29 deletions(-) diff --git a/backend/app/dto/request/website_ssl.go b/backend/app/dto/request/website_ssl.go index 556b6c1a4..509aed434 100644 --- a/backend/app/dto/request/website_ssl.go +++ b/backend/app/dto/request/website_ssl.go @@ -24,6 +24,8 @@ type WebsiteSSLCreate struct { SkipDNS bool `json:"skipDNS"` Nameserver1 string `json:"nameserver1"` Nameserver2 string `json:"nameserver2"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` } type WebsiteDNSReq struct { @@ -87,6 +89,8 @@ type WebsiteSSLUpdate struct { SkipDNS bool `json:"skipDNS"` Nameserver1 string `json:"nameserver1"` Nameserver2 string `json:"nameserver2"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` } type WebsiteSSLUpload struct { @@ -126,6 +130,8 @@ type WebsiteCAObtain struct { Renew bool `json:"renew"` SSLID uint `json:"sslID"` Description string `json:"description"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` } type WebsiteCARenew struct { diff --git a/backend/app/model/website_ssl.go b/backend/app/model/website_ssl.go index a7ad31c0a..15fbb8c4b 100644 --- a/backend/app/model/website_ssl.go +++ b/backend/app/model/website_ssl.go @@ -33,6 +33,8 @@ type WebsiteSSL struct { Nameserver1 string `json:"nameserver1"` Nameserver2 string `json:"nameserver2"` DisableCNAME bool `json:"disableCNAME"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"` DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"` diff --git a/backend/app/service/website_ca.go b/backend/app/service/website_ca.go index ee41d086b..ef37c6d52 100644 --- a/backend/app/service/website_ca.go +++ b/backend/app/service/website_ca.go @@ -16,6 +16,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/i18n" + "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/ssl" @@ -200,6 +201,10 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website CaID: ca.ID, AutoRenew: req.AutoRenew, Description: req.Description, + ExecShell: req.ExecShell, + } + if req.ExecShell { + websiteSSL.Shell = req.Shell } if req.PushDir { if !files.NewFileOp().Stat(req.Dir) { @@ -363,6 +368,18 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website logger := log.New(logFile, "", log.LstdFlags) logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")})) saveCertificateFile(websiteSSL, logger) + if websiteSSL.ExecShell { + workDir := constant.DataDir + if websiteSSL.PushDir { + workDir = websiteSSL.Dir + } + logger.Println(i18n.GetMsgByKey("ExecShellStart")) + if err = cmd.ExecShellWithTimeOut(websiteSSL.Shell, workDir, logger, 30*time.Minute); err != nil { + logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()})) + } else { + logger.Println(i18n.GetMsgByKey("ExecShellSuccess")) + } + } return websiteSSL, nil } diff --git a/backend/app/service/website_ssl.go b/backend/app/service/website_ssl.go index 542d821fd..761903242 100644 --- a/backend/app/service/website_ssl.go +++ b/backend/app/service/website_ssl.go @@ -14,6 +14,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/i18n" + "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/ssl" @@ -127,6 +128,10 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs Nameserver2: create.Nameserver2, SkipDNS: create.SkipDNS, DisableCNAME: create.DisableCNAME, + ExecShell: create.ExecShell, + } + if create.ExecShell { + websiteSSL.Shell = create.Shell } if create.PushDir { fileOP := files.NewFileOp() @@ -293,6 +298,20 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { websiteSSL.Status = constant.SSLReady legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")})) saveCertificateFile(websiteSSL, logger) + + if websiteSSL.ExecShell { + workDir := constant.DataDir + if websiteSSL.PushDir { + workDir = websiteSSL.Dir + } + legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellStart")) + if err = cmd.ExecShellWithTimeOut(websiteSSL.Shell, workDir, logger, 30*time.Minute); err != nil { + legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()})) + } else { + legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellSuccess")) + } + } + err = websiteSSLRepo.Save(websiteSSL) if err != nil { return @@ -407,12 +426,17 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error { updateParams["primary_domain"] = update.PrimaryDomain updateParams["description"] = update.Description updateParams["provider"] = update.Provider - //updateParams["key_type"] = update.KeyType updateParams["push_dir"] = update.PushDir updateParams["disable_cname"] = update.DisableCNAME updateParams["skip_dns"] = update.SkipDNS updateParams["nameserver1"] = update.Nameserver1 updateParams["nameserver2"] = update.Nameserver2 + updateParams["exec_shell"] = update.ExecShell + if update.ExecShell { + updateParams["shell"] = update.Shell + } else { + updateParams["shell"] = "" + } if websiteSSL.Provider != constant.SelfSigned { acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(update.AcmeAccountID)) @@ -423,8 +447,9 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error { } if update.PushDir { - if !files.NewFileOp().Stat(update.Dir) { - return buserr.New(constant.ErrLinkPathNotFound) + fileOP := files.NewFileOp() + if !fileOP.Stat(update.Dir) { + _ = fileOP.CreateDir(update.Dir, 0755) } updateParams["dir"] = update.Dir } diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 252e9539e..e52fc7f6e 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -120,6 +120,9 @@ ErrDefaultCA: "默认机构不能删除" ApplyWebSiteSSLLog: "开始更新 {{ .name }} 网站证书" ErrUpdateWebsiteSSL: "{{ .name }} 网站更新证书失败: {{ .err }}" ApplyWebSiteSSLSuccess: "更新网站证书成功" +ErrExecShell: "执行脚本失败 {{ .err }}" +ExecShellStart: "开始执行脚本" +ExecShellSuccess: "脚本执行成功" #mysql ErrUserIsExist: "当前用户已存在,请重新输入" diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 5f40b8038..d1967295a 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -90,6 +90,7 @@ func Init() { migrations.AddProxy, migrations.AddCronJobColumn, migrations.AddForward, + migrations.AddShellColumn, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/v_1_10.go b/backend/init/migration/migrations/v_1_10.go index 7e72e15d4..f89e93fac 100644 --- a/backend/init/migration/migrations/v_1_10.go +++ b/backend/init/migration/migrations/v_1_10.go @@ -258,3 +258,13 @@ var AddCronJobColumn = &gormigrate.Migration{ return nil }, } + +var AddShellColumn = &gormigrate.Migration{ + ID: "20240620-update-website-ssl", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil { + return err + } + return nil + }, +} diff --git a/backend/utils/cmd/cmd.go b/backend/utils/cmd/cmd.go index 4d2780b5d..ff81054d6 100644 --- a/backend/utils/cmd/cmd.go +++ b/backend/utils/cmd/cmd.go @@ -2,7 +2,10 @@ package cmd import ( "bytes" + "context" + "errors" "fmt" + "log" "os" "os/exec" "strings" @@ -203,3 +206,21 @@ func Which(name string) bool { _, err := exec.LookPath(name) return err == nil } + +func ExecShellWithTimeOut(cmdStr, workdir string, logger *log.Logger, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, "bash", "-c", cmdStr) + cmd.Dir = workdir + cmd.Stdout = logger.Writer() + cmd.Stderr = logger.Writer() + if err := cmd.Start(); err != nil { + return err + } + err := cmd.Wait() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return buserr.New(constant.ErrCmdTimeout) + } + return err +} diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 095f3a5ec..797b9b885 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -192,6 +192,8 @@ export namespace Website { nameserver2: string; disableCNAME: boolean; skipDNS: boolean; + execShell: boolean; + shell: string; } export interface SSLDTO extends SSL { diff --git a/frontend/src/components/log-file/index.vue b/frontend/src/components/log-file/index.vue index ae927e4cd..14f3161ad 100644 --- a/frontend/src/components/log-file/index.vue +++ b/frontend/src/components/log-file/index.vue @@ -246,7 +246,7 @@ const initCodemirror = () => { } }); let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement; - hljsDom.style['min-height'] = '300px'; + hljsDom.style['min-height'] = '500px'; } }); }; @@ -266,7 +266,7 @@ defineExpose({ changeTail, onDownload, clearLog }); .editor-main { height: calc(100vh - 480px); width: 100%; - min-height: 400px; + min-height: 600px; overflow: auto; } diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 43d5e4b73..564c9a1c0 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2156,6 +2156,10 @@ const message = { nameserver: 'DNS server', nameserverHelper: 'Use a custom DNS server to verify domain names', edit: 'Edit certificate', + execShell: 'Execute the script after applying for the certificate', + shell: 'Script content', + shellHelper: + 'The default execution directory of the script is the 1Panel installation directory. If a certificate is pushed, the execution directory is the certificate push directory. The default timeout is 30 minutes', }, firewall: { create: 'Create rule', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index e03de7f2c..ddea7087b 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -2009,6 +2009,10 @@ const message = { nameserver: 'DNS 伺服器', nameserverHelper: '使用自訂的 DNS 伺服器來校驗網域名稱', edit: '編輯證書', + execShell: '申請憑證之後執行腳本', + shell: '腳本內容', + shellHelper: + '腳本預設執行目錄為 1Panel 安裝目錄,如果有推送證書,那麼執行目錄為證書推送目錄。預設超時時間 30 分鐘', }, firewall: { create: '創建規則', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index ff7b74f25..8bce55d51 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2011,6 +2011,10 @@ const message = { nameserver: 'DNS 服务器', nameserverHelper: '使用自定义的 DNS 服务器来校验域名', edit: '编辑证书', + execShell: '申请证书之后执行脚本', + shell: '脚本内容', + shellHelper: + '脚本默认执行目录为 1Panel 安装目录,如果有推送证书,那么执行目录为证书推送目录。默认超时时间 30 分钟', }, firewall: { create: '创建规则', diff --git a/frontend/src/views/website/ssl/ca/obtain/index.vue b/frontend/src/views/website/ssl/ca/obtain/index.vue index 2d681c7e8..c8ef70986 100644 --- a/frontend/src/views/website/ssl/ca/obtain/index.vue +++ b/frontend/src/views/website/ssl/ca/obtain/index.vue @@ -56,6 +56,15 @@ {{ $t('ssl.pushDirHelper') }} + + + + + + + {{ $t('ssl.shellHelper') }} + + @@ -89,6 +98,7 @@ const rules = ref({ domains: [Rules.requiredInput], dir: [Rules.requiredInput], time: [Rules.integerNumber, checkNumberRange(1, 10000)], + shell: [Rules.requiredInput], }); const initData = () => ({ @@ -101,6 +111,8 @@ const initData = () => ({ dir: '', autoRenew: true, description: '', + execShell: false, + shell: '', }); const obtain = ref(initData()); diff --git a/frontend/src/views/website/ssl/create/index.vue b/frontend/src/views/website/ssl/create/index.vue index 50d6e1ed7..24d59bf11 100644 --- a/frontend/src/views/website/ssl/create/index.vue +++ b/frontend/src/views/website/ssl/create/index.vue @@ -31,7 +31,11 @@ - + - + {{ $t('website.dnsAccount') }} {{ $t('website.dnsManual') }} @@ -111,30 +115,41 @@ {{ $t('ssl.pushDirHelper') }} - - + + + + + - {{ $t('ssl.disableCNAMEHelper') }} - - - - - - {{ $t('ssl.skipDNSCheckHelper') }} - - - - - - {{ $t('ssl.nameserverHelper') }} - - - - - - {{ $t('ssl.nameserverHelper') }} + {{ $t('ssl.shellHelper') }} +
+ + + + {{ $t('ssl.disableCNAMEHelper') }} + + + + + + {{ $t('ssl.skipDNSCheckHelper') }} + + + + + + {{ $t('ssl.nameserverHelper') }} + + + + + + {{ $t('ssl.nameserverHelper') }} + + +
@@ -196,6 +211,7 @@ const rules = ref({ dir: [Rules.requiredInput], nameserver1: [Rules.ipv4], nameserver2: [Rules.ipv4], + shell: [Rules.requiredInput], }); const websiteID = ref(); @@ -216,6 +232,8 @@ const initData = () => ({ skipDNS: false, nameserver1: '', nameserver2: '', + execShell: false, + shell: '', }); const ssl = ref(initData()); @@ -257,6 +275,8 @@ const acceptParams = (op: string, websiteSSL: Website.SSLDTO) => { ssl.value.nameserver1 = websiteSSL.nameserver1; ssl.value.nameserver2 = websiteSSL.nameserver2; ssl.value.keyType = websiteSSL.keyType; + ssl.value.execShell = websiteSSL.execShell; + ssl.value.shell = websiteSSL.shell; } ssl.value.websiteId = Number(id.value); getAcmeAccounts(); @@ -348,6 +368,8 @@ const submit = async (formEl: FormInstance | undefined) => { skipDNS: ssl.value.skipDNS, nameserver1: ssl.value.nameserver1, nameserver2: ssl.value.nameserver2, + execShell: ssl.value.execShell, + shell: ssl.value.shell, }; UpdateSSL(sslUpdate) .then(() => { diff --git a/frontend/src/views/website/ssl/index.vue b/frontend/src/views/website/ssl/index.vue index c8ca950a9..a57717f2a 100644 --- a/frontend/src/views/website/ssl/index.vue +++ b/frontend/src/views/website/ssl/index.vue @@ -240,7 +240,7 @@ const buttons = [ onEdit(row); }, show: function (row: Website.SSLDTO) { - return row.provider != 'manual' && row.provider != 'selfSigned'; + return row.provider != 'manual'; }, }, {