1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-31 22:18:07 +08:00

feat: SSL 配置增加重启选项 (#6792)

This commit is contained in:
ssongliu 2024-10-21 22:47:14 +08:00 committed by ssongliu
parent 21cdd05d24
commit e615f3d0fb
14 changed files with 95 additions and 38 deletions

View File

@ -30,6 +30,7 @@ type SettingInfo struct {
ServerPort string `json:"serverPort"` ServerPort string `json:"serverPort"`
SSL string `json:"ssl"` SSL string `json:"ssl"`
SSLType string `json:"sslType"` SSLType string `json:"sslType"`
AutoRestart string `json:"autoRestart"`
BindDomain string `json:"bindDomain"` BindDomain string `json:"bindDomain"`
AllowIPs string `json:"allowIPs"` AllowIPs string `json:"allowIPs"`
SecurityEntrance string `json:"securityEntrance"` SecurityEntrance string `json:"securityEntrance"`
@ -79,6 +80,7 @@ type SSLUpdate struct {
Cert string `json:"cert"` Cert string `json:"cert"`
Key string `json:"key"` Key string `json:"key"`
SSLID uint `json:"sslID"` SSLID uint `json:"sslID"`
AutoRestart string `json:"autoRestart"`
} }
type SSLInfo struct { type SSLInfo struct {
Domain string `json:"domain"` Domain string `json:"domain"`

View File

@ -322,6 +322,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := settingRepo.Update("SSL", req.SSL); err != nil { if err := settingRepo.Update("SSL", req.SSL); err != nil {
return err return err
} }
if err := settingRepo.Update("AutoRestart", req.AutoRestart); err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName) sID, _ := c.Cookie(constant.SessionName)
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true) c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)

View File

@ -3,7 +3,6 @@ package service
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"log" "log"
"os" "os"
"path" "path"
@ -13,6 +12,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/i18n"
@ -1001,20 +1002,23 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
} }
} }
func GetSystemSSL() (bool, uint) { func GetSystemSSL() (bool, bool, uint) {
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL")) sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil { if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err) global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
return false, 0 return false, false, 0
} }
if sslSetting.Value == "enable" { if sslSetting.Value == "enable" {
sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID")) sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID"))
idValue, _ := strconv.Atoi(sslID.Value) idValue, _ := strconv.Atoi(sslID.Value)
if idValue > 0 { if idValue <= 0 {
return true, uint(idValue) return false, false, 0
} }
auto, _ := settingRepo.Get(settingRepo.WithByKey("AutoRestart"))
return true, auto.Value == "enable", uint(idValue)
} }
return false, 0 return false, false, 0
} }
func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error { func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
@ -1033,7 +1037,7 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
return buserr.WithErr(constant.ErrSSLApply, err) return buserr.WithErr(constant.ErrSSLApply, err)
} }
} }
enable, sslID := GetSystemSSL() enable, auto, sslID := GetSystemSSL()
if enable && sslID == websiteSSL.ID { if enable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret") secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
@ -1045,6 +1049,9 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error()) global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return err return err
} }
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
} }
return nil return nil
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/service" "github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
) )
@ -22,7 +23,7 @@ func NewSSLJob() *ssl {
} }
func (ssl *ssl) Run() { func (ssl *ssl) Run() {
systemSSLEnable, sslID := service.GetSystemSSL() systemSSLEnable, auto, sslID := service.GetSystemSSL()
sslRepo := repo.NewISSLRepo() sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService() sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List() sslList, _ := sslRepo.List()
@ -70,6 +71,9 @@ func (ssl *ssl) Run() {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error()) global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue continue
} }
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
} }
global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain) global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
} }

View File

@ -95,6 +95,8 @@ func Init() {
migrations.AddClamStatus, migrations.AddClamStatus,
migrations.AddAlertMenu, migrations.AddAlertMenu,
migrations.AddComposeColumn, migrations.AddComposeColumn,
migrations.AddAutoRestart,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -324,3 +324,13 @@ var AddComposeColumn = &gormigrate.Migration{
return nil return nil
}, },
} }
var AddAutoRestart = &gormigrate.Migration{
ID: "20241021-add-auto-restart",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "AutoRestart", Value: "enable"}).Error; err != nil {
return err
}
return nil
},
}

View File

@ -29,6 +29,7 @@ export namespace Setting {
bindAddress: string; bindAddress: string;
ssl: string; ssl: string;
sslType: string; sslType: string;
autoRestart: string;
allowIPs: string; allowIPs: string;
bindDomain: string; bindDomain: string;
securityEntrance: string; securityEntrance: string;

View File

@ -4,6 +4,7 @@
:destroy-on-close="true" :destroy-on-close="true"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
:before-close="handleClose"
:size="globalStore.isFullScreen ? '100%' : '50%'" :size="globalStore.isFullScreen ? '100%' : '50%'"
> >
<template #header> <template #header>
@ -21,10 +22,10 @@
</el-drawer> </el-drawer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, ref, watch } from 'vue';
import LogFile from '@/components/log-file/index.vue'; import LogFile from '@/components/log-file/index.vue';
import { GlobalStore } from '@/store';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
import { GlobalStore } from '@/store';
import i18n from '@/lang'; import i18n from '@/lang';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
@ -45,19 +46,22 @@ const open = ref(false);
const config = ref(); const config = ref();
const em = defineEmits(['close']); const em = defineEmits(['close']);
const handleClose = (search: boolean) => { const handleClose = () => {
open.value = false; open.value = false;
em('close', search); em('close', false);
globalStore.isFullScreen = false;
}; };
watch(open, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
});
function toggleFullscreen() { function toggleFullscreen() {
if (screenfull.isEnabled) { globalStore.isFullScreen = !globalStore.isFullScreen;
screenfull.toggle();
}
} }
const loadTooltip = () => { const loadTooltip = () => {
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen')); return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
}; };
const acceptParams = (props: LogProps) => { const acceptParams = (props: LogProps) => {

View File

@ -1517,8 +1517,11 @@ const message = {
bindDomain: 'Bind Domain', bindDomain: 'Bind Domain',
unBindDomain: 'Unbind domain', unBindDomain: 'Unbind domain',
panelSSL: 'Panel SSL', panelSSL: 'Panel SSL',
panelSSLHelper: sslAutoRestart: 'Restart 1Panel service after certificate auto-renewal',
'After the automatic renewal of the panel SSL, you need to manually restart the 1Panel service for the changes to take effect.', sslChangeHelper1:
'Currently, automatic restart of 1Panel service is not selected. The certificate auto-renewal will not take effect immediately and will still require a manual restart of 1Panel.',
sslChangeHelper2:
'The 1Panel service will automatically restart after setting the panel SSL. Do you want to continue?',
unBindDomainHelper: unBindDomainHelper:
'The action of unbinding a domain name may cause system insecurity. Do you want to continue?', 'The action of unbinding a domain name may cause system insecurity. Do you want to continue?',
bindDomainHelper: bindDomainHelper:

View File

@ -1467,7 +1467,9 @@ const message = {
bindDomain: '域名綁定', bindDomain: '域名綁定',
unBindDomain: '域名解綁', unBindDomain: '域名解綁',
panelSSL: '面板 SSL', panelSSL: '面板 SSL',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效', sslAutoRestart: '證書自動續期後重啟 1Panel 服務',
sslChangeHelper1: '當前未勾選自動重啟 1Panel 服務證書自動續期後不會立即生效仍需手動重啟 1Panel',
sslChangeHelper2: '設置面板 SSL 後將自動重啟 1Panel 服務是否繼續',
unBindDomainHelper: '解除域名綁定可能造成系統不安全是否繼續', unBindDomainHelper: '解除域名綁定可能造成系統不安全是否繼續',
bindDomainHelper: '設置域名綁定後僅能通過設置中域名訪問 1Panel 服務', bindDomainHelper: '設置域名綁定後僅能通過設置中域名訪問 1Panel 服務',
bindDomainHelper1: '綁定域名為空時則取消域名綁定', bindDomainHelper1: '綁定域名為空時則取消域名綁定',

View File

@ -1469,7 +1469,9 @@ const message = {
bindDomain: '域名绑定', bindDomain: '域名绑定',
unBindDomain: '域名解绑', unBindDomain: '域名解绑',
panelSSL: '面板 SSL', panelSSL: '面板 SSL',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效', sslAutoRestart: '证书自动续期后重启 1Panel 服务',
sslChangeHelper1: '当前未勾选自动重启 1Panel 服务证书自动续期后不会立即生效仍需手动重启 1Panel',
sslChangeHelper2: '设置面板 SSL 后将自动重启 1Panel 服务是否继续',
unBindDomainHelper: '解除域名绑定可能造成系统不安全是否继续', unBindDomainHelper: '解除域名绑定可能造成系统不安全是否继续',
bindDomainHelper: '设置域名绑定后仅能通过设置中域名访问 1Panel 服务', bindDomainHelper: '设置域名绑定后仅能通过设置中域名访问 1Panel 服务',
bindDomainHelper1: '绑定域名为空时则取消域名绑定', bindDomainHelper1: '绑定域名为空时则取消域名绑定',
@ -1493,7 +1495,6 @@ const message = {
mfaInterval: '刷新时间', mfaInterval: '刷新时间',
mfaTitleHelper: '用于区分不同 1Panel 主机修改后请重新扫描或手动添加密钥信息', mfaTitleHelper: '用于区分不同 1Panel 主机修改后请重新扫描或手动添加密钥信息',
mfaIntervalHelper: '修改刷新时间后请重新扫描或手动添加密钥信息', mfaIntervalHelper: '修改刷新时间后请重新扫描或手动添加密钥信息',
sslChangeHelper: 'https 设置修改需要重启服务是否继续',
sslDisable: '禁用', sslDisable: '禁用',
sslDisableHelper: '禁用 https 服务需要重启面板才能生效是否继续', sslDisableHelper: '禁用 https 服务需要重启面板才能生效是否继续',
noAuthSetting: '未认证设置', noAuthSetting: '未认证设置',

View File

@ -114,7 +114,7 @@
<div v-if="form.ssl === 'enable' && sslInfo"> <div v-if="form.ssl === 'enable' && sslInfo">
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag> <el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag>
<el-tag style="margin-left: 5px"> <el-tag style="margin-left: 5px">
{{ $t('setting.timeOut') }} {{ sslInfo.timeout }} {{ $t('setting.timeOut') }} {{ dateFormat('', '', sslInfo.timeout) }}
</el-tag> </el-tag>
<div> <div>
<el-button link type="primary" @click="handleSSL"> <el-button link type="primary" @click="handleSSL">
@ -194,6 +194,7 @@ import TimeoutSetting from '@/views/setting/safe/timeout/index.vue';
import EntranceSetting from '@/views/setting/safe/entrance/index.vue'; import EntranceSetting from '@/views/setting/safe/entrance/index.vue';
import DomainSetting from '@/views/setting/safe/domain/index.vue'; import DomainSetting from '@/views/setting/safe/domain/index.vue';
import AllowIPsSetting from '@/views/setting/safe/allowips/index.vue'; import AllowIPsSetting from '@/views/setting/safe/allowips/index.vue';
import { dateFormat } from '@/utils/util';
import { updateSetting, getSettingInfo, getSystemAvailable, updateSSL, loadSSLInfo } from '@/api/modules/setting'; import { updateSetting, getSettingInfo, getSystemAvailable, updateSSL, loadSSLInfo } from '@/api/modules/setting';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
@ -224,6 +225,7 @@ const form = reactive({
bindAddress: '', bindAddress: '',
ssl: 'disable', ssl: 'disable',
sslType: 'self', sslType: 'self',
autoRestart: 'disable',
securityEntrance: '', securityEntrance: '',
expirationDays: 0, expirationDays: 0,
expirationTime: '', expirationTime: '',
@ -248,6 +250,7 @@ const search = async () => {
if (form.ssl === 'enable') { if (form.ssl === 'enable') {
loadInfo(); loadInfo();
} }
form.autoRestart = res.data.autoRestart;
form.securityEntrance = res.data.securityEntrance; form.securityEntrance = res.data.securityEntrance;
form.expirationDays = Number(res.data.expirationDays); form.expirationDays = Number(res.data.expirationDays);
form.expirationTime = res.data.expirationTime; form.expirationTime = res.data.expirationTime;
@ -327,6 +330,7 @@ const handleSSL = async () => {
ssl: form.ssl, ssl: form.ssl,
sslType: form.sslType, sslType: form.sslType,
sslInfo: sslInfo.value, sslInfo: sslInfo.value,
autoRestart: form.autoRestart,
}; };
sslRef.value!.acceptParams(params); sslRef.value!.acceptParams(params);
return; return;

View File

@ -11,13 +11,6 @@
<template #header> <template #header>
<DrawerHeader :header="$t('setting.panelSSL')" :back="handleClose" /> <DrawerHeader :header="$t('setting.panelSSL')" :back="handleClose" />
</template> </template>
<el-alert class="common-prompt" :closable="false" type="error">
<template #default>
<span>
<span>{{ $t('setting.panelSSLHelper') }}</span>
</span>
</template>
</el-alert>
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" v-loading="loading"> <el-form ref="formRef" label-position="top" :model="form" :rules="rules" v-loading="loading">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-col :span="22"> <el-col :span="22">
@ -40,7 +33,9 @@
<el-form-item v-if="form.timeout"> <el-form-item v-if="form.timeout">
<el-tag>{{ $t('setting.domainOrIP') }} {{ form.domain }}</el-tag> <el-tag>{{ $t('setting.domainOrIP') }} {{ form.domain }}</el-tag>
<el-tag style="margin-left: 5px">{{ $t('setting.timeOut') }} {{ form.timeout }}</el-tag> <el-tag style="margin-left: 5px">
{{ $t('setting.timeOut') }} {{ dateFormat('', '', form.timeout) }}
</el-tag>
<el-button <el-button
@click="onDownload" @click="onDownload"
style="margin-left: 5px" style="margin-left: 5px"
@ -117,6 +112,14 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
<el-form-item v-if="form.sslType !== 'import'">
<el-checkbox true-value="enable" false-value="disable" v-model="form.autoRestart">
{{ $t('setting.sslAutoRestart') }}
</el-checkbox>
<span v-if="form.autoRestart === 'disable'" class="input-help">
{{ $t('setting.sslChangeHelper1') }}
</span>
</el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
@ -133,7 +136,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Website } from '@/api/interface/website'; import { Website } from '@/api/interface/website';
import { dateFormatSimple, getProvider } from '@/utils/util'; import { dateFormatSimple, dateFormat, getProvider } from '@/utils/util';
import { ListSSL } from '@/api/modules/website'; import { ListSSL } from '@/api/modules/website';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import i18n from '@/lang'; import i18n from '@/lang';
@ -159,6 +162,7 @@ const form = reactive({
key: '', key: '',
rootPath: '', rootPath: '',
timeout: '', timeout: '',
autoRestart: 'disable',
}); });
const rules = reactive({ const rules = reactive({
@ -175,6 +179,7 @@ const itemSSL = ref();
interface DialogProps { interface DialogProps {
sslType: string; sslType: string;
sslInfo?: Setting.SSLInfo; sslInfo?: Setting.SSLInfo;
autoRestart: string;
} }
const acceptParams = async (params: DialogProps): Promise<void> => { const acceptParams = async (params: DialogProps): Promise<void> => {
if (params.sslType.indexOf('-') !== -1) { if (params.sslType.indexOf('-') !== -1) {
@ -197,6 +202,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
} else { } else {
loadSSLs(); loadSSLs();
} }
form.autoRestart = params.autoRestart;
drawerVisible.value = true; drawerVisible.value = true;
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
@ -237,7 +243,10 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
ElMessageBox.confirm(i18n.global.t('setting.sslChangeHelper'), 'https', { let msg = !form.autoRestart
? i18n.global.t('setting.sslChangeHelper1') + '\n\n\n' + 'qwdqwdqwd'
: i18n.global.t('setting.sslChangeHelper2');
ElMessageBox.confirm(msg, i18n.global.t('setting.panelSSL'), {
confirmButtonText: i18n.global.t('commons.button.confirm'), confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'), cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info', type: 'info',
@ -253,6 +262,7 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
sslID: form.sslID, sslID: form.sslID,
cert: form.cert, cert: form.cert,
key: form.key, key: form.key,
autoRestart: form.autoRestart,
}; };
let href = window.location.href; let href = window.location.href;
param.domain = href.split('//')[1].split(':')[0]; param.domain = href.split('//')[1].split(':')[0];

View File

@ -92,6 +92,10 @@ let timer: NodeJS.Timer | null = null;
const em = defineEmits(['search']); const em = defineEmits(['search']);
watch(open, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
});
const mobile = computed(() => { const mobile = computed(() => {
return globalStore.isMobile(); return globalStore.isMobile();
}); });