From 52b16f0cb4cd94876156c58b56e7e1ec5c46868a Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:16:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AF=81=E4=B9=A6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E6=97=A5=E5=BF=97=20(#3002)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/dto/response/website_ssl.go | 1 + backend/app/service/website_ssl.go | 57 ++++++++++++++++--- backend/constant/dir.go | 1 + backend/cron/job/ssl.go | 2 +- backend/i18n/lang/en.yaml | 7 +++ backend/i18n/lang/zh-Hant.yaml | 7 +++ backend/i18n/lang/zh.yaml | 7 +++ backend/init/app/app.go | 4 +- backend/init/business/business.go | 7 +++ frontend/src/api/interface/website.ts | 6 +- frontend/src/api/modules/website.ts | 4 +- .../runtime/php => components}/log/index.vue | 0 frontend/src/lang/modules/en.ts | 2 +- frontend/src/lang/modules/tw.ts | 2 +- frontend/src/lang/modules/zh.ts | 2 +- .../src/views/website/runtime/php/index.vue | 2 +- frontend/src/views/website/ssl/index.vue | 51 +++++++++-------- 17 files changed, 122 insertions(+), 40 deletions(-) rename frontend/src/{views/website/runtime/php => components}/log/index.vue (100%) diff --git a/backend/app/dto/response/website_ssl.go b/backend/app/dto/response/website_ssl.go index 86a000adf..c501536e5 100644 --- a/backend/app/dto/response/website_ssl.go +++ b/backend/app/dto/response/website_ssl.go @@ -4,6 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/model" type WebsiteSSLDTO struct { model.WebsiteSSL + LogPath string `json:"logPath"` } type WebsiteDNSRes struct { diff --git a/backend/app/service/website_ssl.go b/backend/app/service/website_ssl.go index 7b8f2f69b..b237606d2 100644 --- a/backend/app/service/website_ssl.go +++ b/backend/app/service/website_ssl.go @@ -4,6 +4,7 @@ import ( "context" "crypto/x509" "encoding/pem" + "fmt" "github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/model" @@ -11,11 +12,15 @@ import ( "github.com/1Panel-dev/1Panel/backend/buserr" "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/common" "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/ssl" "github.com/go-acme/lego/v4/certcrypto" + legoLogger "github.com/go-acme/lego/v4/log" "github.com/jinzhu/gorm" + "log" + "os" "path" "strconv" "strings" @@ -37,6 +42,7 @@ type IWebsiteSSLService interface { Update(update request.WebsiteSSLUpdate) error Upload(req request.WebsiteSSLUpload) error ObtainSSL(apply request.WebsiteSSLApply) error + SyncForRestart() error } func NewIWebsiteSSLService() IWebsiteSSLService { @@ -51,9 +57,10 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo if err != nil { return 0, nil, err } - for _, sslModel := range sslList { + for _, model := range sslList { result = append(result, response.WebsiteSSLDTO{ - WebsiteSSL: sslModel, + WebsiteSSL: model, + LogPath: path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", model.PrimaryDomain, model.ID)), }) } return total, result, err @@ -141,11 +148,19 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs } func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { - websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(apply.ID)) + + var ( + err error + websiteSSL model.WebsiteSSL + acmeAccount *model.WebsiteAcmeAccount + dnsAccount *model.WebsiteDnsAccount + ) + + websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(apply.ID)) if err != nil { return err } - acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(websiteSSL.AcmeAccountID)) + acmeAccount, err = websiteAcmeRepo.GetFirst(commonRepo.WithByID(websiteSSL.AcmeAccountID)) if err != nil { return err } @@ -156,11 +171,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { switch websiteSSL.Provider { case constant.DNSAccount: - dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(websiteSSL.DnsAccountID)) + dnsAccount, err = websiteDnsRepo.GetFirst(commonRepo.WithByID(websiteSSL.DnsAccountID)) if err != nil { return err } - if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil { + if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil { return err } case constant.Http: @@ -181,7 +196,9 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { } domains := []string{websiteSSL.PrimaryDomain} - domains = append(domains, domains...) + if websiteSSL.Domains != "" { + domains = append(domains, strings.Split(websiteSSL.Domains, ",")...) + } privateKey, err := certcrypto.GeneratePrivateKey(ssl.KeyType(websiteSSL.KeyType)) if err != nil { @@ -195,6 +212,15 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { } go func() { + logFile, _ := os.OpenFile(path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + defer logFile.Close() + logger := log.New(logFile, "", log.LstdFlags) + legoLogger.Logger = logger + startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)}) + if websiteSSL.Provider == constant.DNSAccount { + startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type}) + } + legoLogger.Logger.Println(startMsg) resource, err := client.ObtainSSL(domains, privateKey) if err != nil { handleError(websiteSSL, err) @@ -214,6 +240,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { websiteSSL.Type = cert.Issuer.CommonName websiteSSL.Organization = cert.Issuer.Organization[0] websiteSSL.Status = constant.SSLReady + legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")})) err = websiteSSLRepo.Save(websiteSSL) if err != nil { return @@ -230,6 +257,7 @@ func handleError(websiteSSL model.WebsiteSSL, err error) { websiteSSL.Status = constant.SSLApplyError } websiteSSL.Message = err.Error() + legoLogger.Logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "err": err.Error()})) _ = websiteSSLRepo.Save(websiteSSL) } @@ -425,3 +453,18 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error { return websiteSSLRepo.Create(context.Background(), newSSL) } + +func (w WebsiteSSLService) SyncForRestart() error { + sslList, err := websiteSSLRepo.List() + if err != nil { + return err + } + for _, ssl := range sslList { + if ssl.Status == constant.SSLApply { + ssl.Status = constant.SystemRestart + ssl.Message = "System restart causing interrupt" + _ = websiteSSLRepo.Save(ssl) + } + } + return nil +} diff --git a/backend/constant/dir.go b/backend/constant/dir.go index 87c348913..1b474a63e 100644 --- a/backend/constant/dir.go +++ b/backend/constant/dir.go @@ -16,4 +16,5 @@ var ( RemoteAppResourceDir = path.Join(AppResourceDir, "remote") RuntimeDir = path.Join(DataDir, "runtime") RecycleBinDir = "/.1panel_clash" + SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl") ) diff --git a/backend/cron/job/ssl.go b/backend/cron/job/ssl.go index 585b20159..a92979c9b 100644 --- a/backend/cron/job/ssl.go +++ b/backend/cron/job/ssl.go @@ -23,7 +23,7 @@ func (ssl *ssl) Run() { global.LOG.Info("The scheduled certificate update task is currently in progress ...") now := time.Now().Add(10 * time.Second) for _, s := range sslList { - if !s.AutoRenew || s.Provider == "manual" || s.Provider == "dnsManual" { + if !s.AutoRenew || s.Provider == "manual" || s.Provider == "dnsManual" || s.Status == "applying" { continue } expireDate := s.ExpireDate.In(nyc) diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index c69445731..9b52eec48 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -93,6 +93,13 @@ ErrSSLKeyFormat: 'Private key file verification error' ErrSSLCertificateFormat: 'Certificate file format error, please use pem format' ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be empty' ErrOpenrestyNotFound: 'Http mode requires Openresty to be installed first' +ApplySSLStart: 'Start applying for certificate, domain name [{{ .domain }}] application method [{{ .type }}] ' +dnsAccount: "DNS Automatic" +dnsManual: "DNS Manual" +http: "HTTP" +ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{.detail}} ' +ApplySSLSuccess: 'Application for [{{ .domain }}] certificate successful! ! ' +DNSAccountName: 'DNS account [{{ .name }}] manufacturer [{{.type}}]' #mysql diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index ebf43eba8..1b0af98c6 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -93,6 +93,13 @@ ErrSSLKeyFormat: '私鑰文件校驗錯誤' ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式' ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能為空' ErrOpenrestyNotFound: 'Http 模式需要先安裝 Openresty' +ApplySSLStart: '開始申請憑證,網域 [{{ .domain }}] 申請方式 [{{ .type }}] ' +dnsAccount: "DNS 自動" +dnsManual: "DNS 手排" +http: "HTTP" +ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} ' +ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! ! ' +DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]' #mysql ErrUserIsExist: "當前用戶已存在,請重新輸入" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index c6e9be84d..31b704ad6 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -93,6 +93,13 @@ ErrSSLKeyFormat: '私钥文件校验失败' ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式' ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空' ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty' +ApplySSLStart: '开始申请证书,域名 [{{ .domain }}] 申请方式 [{{ .type }}] ' +dnsAccount: "DNS 自动" +dnsManual: "DNS 手动" +http: "HTTP" +ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} ' +ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!' +DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]' #mysql ErrUserIsExist: "当前用户已存在,请重新输入" diff --git a/backend/init/app/app.go b/backend/init/app/app.go index 34fb137d8..45fa7f2bc 100644 --- a/backend/init/app/app.go +++ b/backend/init/app/app.go @@ -20,8 +20,10 @@ func Init() { constant.LocalAppInstallDir = path.Join(constant.AppInstallDir, "local") constant.RemoteAppResourceDir = path.Join(constant.AppResourceDir, "remote") + constant.SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl") + dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir, - global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir} + global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir, constant.SSLLogDir} fileOp := files.NewFileOp() for _, dir := range dirs { diff --git a/backend/init/business/business.go b/backend/init/business/business.go index 2e9cbd115..c58cce1b1 100644 --- a/backend/init/business/business.go +++ b/backend/init/business/business.go @@ -10,6 +10,7 @@ func Init() { go syncApp() go syncInstalledApp() go syncRuntime() + go syncSSL() } func syncApp() { @@ -31,3 +32,9 @@ func syncRuntime() { global.LOG.Errorf("sync runtime status error : %s", err.Error()) } } + +func syncSSL() { + if err := service.NewIWebsiteSSLService().SyncForRestart(); err != nil { + global.LOG.Errorf("sync ssl status error : %s", err.Error()) + } +} diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index e5951d573..e032b889b 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -165,7 +165,11 @@ export namespace Website { autoRenew: boolean; acmeAccountId?: number; status: string; - domains?: string; + } + + export interface SSLDTO extends SSL { + domains: string; + logPath: string; } export interface SSLCreate { diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index de6b3d8f9..762c5e10d 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -93,11 +93,11 @@ export const DeleteAcmeAccount = (req: Website.DelReq) => { }; export const SearchSSL = (req: ReqPage) => { - return http.post>(`/websites/ssl/search`, req); + return http.post>(`/websites/ssl/search`, req); }; export const ListSSL = (req: Website.SSLReq) => { - return http.post(`/websites/ssl/search`, req); + return http.post(`/websites/ssl/search`, req); }; export const CreateSSL = (req: Website.SSLCreate) => { diff --git a/frontend/src/views/website/runtime/php/log/index.vue b/frontend/src/components/log/index.vue similarity index 100% rename from frontend/src/views/website/runtime/php/log/index.vue rename to frontend/src/components/log/index.vue diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index fc7f6974d..602a74af2 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1765,7 +1765,7 @@ const message = { saveAndReload: 'Save and Reload', }, ssl: { - create: 'Create Certificate', + create: 'Apply Certificate', provider: 'Type', manualCreate: 'manually created', acmeAccount: 'Acme Account', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 7a8a19c8d..feea8e23e 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1662,7 +1662,7 @@ const message = { saveAndReload: '保存並重載', }, ssl: { - create: '創建證書', + create: '申請證書', provider: '類型', manualCreate: '手動創建', acmeAccount: 'Acme 賬號', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 8047692e1..db0c12c3f 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1662,7 +1662,7 @@ const message = { saveAndReload: '保存并重载', }, ssl: { - create: '创建证书', + create: '申请证书', provider: '类型', manualCreate: '手动创建', acmeAccount: 'Acme 账号', diff --git a/frontend/src/views/website/runtime/php/index.vue b/frontend/src/views/website/runtime/php/index.vue index 46873e29d..eadbc20f5 100644 --- a/frontend/src/views/website/runtime/php/index.vue +++ b/frontend/src/views/website/runtime/php/index.vue @@ -87,7 +87,7 @@ import CreateRuntime from '@/views/website/runtime/php/create/index.vue'; import Status from '@/components/status/index.vue'; import i18n from '@/lang'; import RouterMenu from '../index.vue'; -import Log from '@/views/website/runtime/php/log/index.vue'; +import Log from '@/components/log/index.vue'; const paginationConfig = reactive({ cacheSizeKey: 'runtime-page-size', diff --git a/frontend/src/views/website/ssl/index.vue b/frontend/src/views/website/ssl/index.vue index 868c29c76..0052499dd 100644 --- a/frontend/src/views/website/ssl/index.vue +++ b/frontend/src/views/website/ssl/index.vue @@ -81,6 +81,11 @@ + + + + @@ -137,6 +143,7 @@ import { MsgSuccess } from '@/utils/message'; import { GlobalStore } from '@/store'; import SSLUpload from './upload/index.vue'; import Apply from './apply/index.vue'; +import Log from '@/components/log/index.vue'; const globalStore = GlobalStore(); const paginationConfig = reactive({ @@ -148,13 +155,13 @@ const paginationConfig = reactive({ const acmeAccountRef = ref(); const dnsAccountRef = ref(); const sslCreateRef = ref(); -const renewRef = ref(); const detailRef = ref(); const data = ref(); const loading = ref(false); const opRef = ref(); const sslUploadRef = ref(); const applyRef = ref(); +const logRef = ref(); const routerButton = [ { @@ -166,19 +173,19 @@ const routerButton = [ const buttons = [ { label: i18n.global.t('ssl.detail'), - disabled: function (row: Website.SSL) { + disabled: function (row: Website.SSLDTO) { return row.status === 'init' || row.status === 'error'; }, - click: function (row: Website.SSL) { + click: function (row: Website.SSLDTO) { openDetail(row.id); }, }, { label: i18n.global.t('ssl.apply'), - disabled: function (row: Website.SSL) { + disabled: function (row: Website.SSLDTO) { return row.status === 'applying'; }, - click: function (row: Website.SSL) { + click: function (row: Website.SSLDTO) { if (row.provider === 'dnsManual') { applyRef.value.acceptParams({ ssl: row }); } else { @@ -188,7 +195,7 @@ const buttons = [ }, { label: i18n.global.t('commons.button.delete'), - click: function (row: Website.SSL) { + click: function (row: Website.SSLDTO) { deleteSSL(row); }, }, @@ -214,7 +221,7 @@ const search = () => { }); }; -const updateConfig = (row: Website.SSL) => { +const updateConfig = (row: Website.SSLDTO) => { loading.value = true; UpdateSSL({ id: row.id, autoRenew: row.autoRenew }) .then(() => { @@ -237,27 +244,23 @@ const openSSL = () => { const openUpload = () => { sslUploadRef.value.acceptParams(); }; -const openRenewSSL = (id: number, websites: Website.Website[]) => { - renewRef.value.acceptParams({ id: id, websites: websites }); -}; const openDetail = (id: number) => { detailRef.value.acceptParams(id); }; +const openLog = (row: Website.SSLDTO) => { + logRef.value.acceptParams({ path: row.logPath }); +}; -const applySSL = (row: Website.SSL) => { - if (row.status === 'init' || row.status === 'error') { - loading.value = true; - ObtainSSL({ ID: row.id }) - .then(() => { - MsgSuccess(i18n.global.t('ssl.applyStart')); - search(); - }) - .finally(() => { - loading.value = false; - }); - } else { - openRenewSSL(row.id, row.websites); - } +const applySSL = (row: Website.SSLDTO) => { + loading.value = true; + ObtainSSL({ ID: row.id }) + .then(() => { + MsgSuccess(i18n.global.t('ssl.applyStart')); + search(); + }) + .finally(() => { + loading.value = false; + }); }; const deleteSSL = async (row: any) => {