fix: Update compression backup exclusion rules (#9323)

This commit is contained in:
ssongliu 2025-06-27 17:25:23 +08:00 committed by GitHub
parent 64fd715973
commit be6d3ea337
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 85 additions and 94 deletions

View file

@ -23,8 +23,6 @@ type SettingInfo struct {
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
FileRecycleBin string `json:"fileRecycleBin"`
SnapshotIgnore string `json:"snapshotIgnore"`
}
type SettingUpdate struct {

View file

@ -28,6 +28,8 @@ type SnapshotCreate struct {
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
IgnoreFiles []string `json:"ignoreFiles"`
}
type SnapshotData struct {
@ -35,11 +37,12 @@ type SnapshotData struct {
BackupData []DataTree `json:"backupData"`
PanelData []DataTree `json:"panelData"`
WithMonitorData bool `json:"withMonitorData"`
WithLoginLog bool `json:"withLoginLog"`
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
WithMonitorData bool `json:"withMonitorData"`
WithLoginLog bool `json:"withLoginLog"`
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
IgnoreFiles []string `json:"ignoreFiles"`
}
type DataTree struct {
ID string `json:"id"`

View file

@ -23,6 +23,7 @@ type Snapshot struct {
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
IgnoreFiles string `json:"ignoreFiles"`
InterruptStep string `json:"interruptStep"`
RecoverStatus string `json:"recoverStatus"`

View file

@ -285,6 +285,7 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
WithOperationLog: true,
WithSystemLog: true,
WithTaskLog: true,
IgnoreFiles: strings.Split(cronjob.ExclusionRules, ","),
}
if err := NewISnapshotService().SnapshotCreate(taskItem, req, jobRecord.ID, cronjob.RetryTimes, cronjob.Timeout); err != nil {

View file

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
@ -55,6 +56,7 @@ func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.Snapshot
WithOperationLog: req.WithOperationLog,
WithTaskLog: req.WithTaskLog,
WithSystemLog: req.WithSystemLog,
IgnoreFiles: strings.Join(req.IgnoreFiles, ","),
Version: versionItem.Value,
Status: constant.StatusWaiting,
@ -398,6 +400,7 @@ func snapBackupData(snap snapHelper, req dto.SnapshotCreate, targetDir string) e
snap.Task.LogStart(i18n.GetMsgByKey("SnapLocalBackup"))
excludes := loadBackupExcludes(snap, req.BackupData)
excludes = append(excludes, req.IgnoreFiles...)
excludes = append(excludes, "./system_snapshot")
for _, item := range req.AppData {
for _, itemApp := range item.Children {
@ -421,7 +424,8 @@ func loadBackupExcludes(snap snapHelper, req []dto.DataTree) []string {
if err := snap.snapAgentDB.Where("file_dir = ? AND file_name = ?", strings.TrimPrefix(path.Dir(item.Path), global.Dir.LocalBackupDir+"/"), path.Base(item.Path)).Delete(&model.BackupRecord{}).Error; err != nil {
snap.Task.LogWithStatus("delete backup file from database", err)
}
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, global.Dir.LocalBackupDir))
itemDir, _ := filepath.Rel(item.Path, global.Dir.LocalBackupDir)
excludes = append(excludes, itemDir)
} else {
excludes = append(excludes, loadBackupExcludes(snap, item.Children)...)
}
@ -433,7 +437,8 @@ func loadAppBackupExcludes(req []dto.DataTree) []string {
for _, item := range req {
if len(item.Children) == 0 {
if !item.IsCheck {
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, path.Join(global.Dir.LocalBackupDir)))
itemDir, _ := filepath.Rel(item.Path, global.Dir.LocalBackupDir)
excludes = append(excludes, itemDir)
}
} else {
excludes = append(excludes, loadAppBackupExcludes(item.Children)...)
@ -466,26 +471,21 @@ func snapPanelData(snap snapHelper, req dto.SnapshotCreate, targetDir string) er
rootDir := global.Dir.DataDir
if strings.Contains(global.Dir.LocalBackupDir, rootDir) {
excludes = append(excludes, "."+strings.ReplaceAll(global.Dir.LocalBackupDir, rootDir, ""))
itemDir, _ := filepath.Rel(rootDir, global.Dir.LocalBackupDir)
excludes = append(excludes, itemDir)
}
if len(snap.OpenrestyDir) != 0 && strings.Contains(snap.OpenrestyDir, rootDir) {
excludes = append(excludes, "."+strings.ReplaceAll(snap.OpenrestyDir, rootDir, ""))
}
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
rules := strings.Split(ignoreVal.Value, ",")
for _, ignore := range rules {
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
continue
}
excludes = append(excludes, "."+strings.ReplaceAll(ignore, rootDir, ""))
itemDir, _ := filepath.Rel(rootDir, snap.OpenrestyDir)
excludes = append(excludes, itemDir)
}
excludes = append(excludes, req.IgnoreFiles...)
err := snap.FileOp.TarGzCompressPro(false, rootDir, path.Join(targetDir, "1panel_data.tar.gz"), "", strings.Join(excludes, ","))
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressPanel"), err)
if err != nil {
return err
}
if len(snap.OpenrestyDir) != 0 {
err := snap.FileOp.TarGzCompressPro(false, snap.OpenrestyDir, path.Join(targetDir, "website.tar.gz"), "", "")
err := snap.FileOp.TarGzCompressPro(false, snap.OpenrestyDir, path.Join(targetDir, "website.tar.gz"), "", strings.Join(req.IgnoreFiles, ","))
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapWebsite"), err)
if err != nil {
return err
@ -499,7 +499,8 @@ func loadPanelExcludes(req []dto.DataTree) []string {
for _, item := range req {
if len(item.Children) == 0 {
if !item.IsCheck {
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, path.Join(global.Dir.BaseDir, "1panel")))
itemDir, _ := filepath.Rel(item.Path, path.Join(global.Dir.BaseDir, "1panel"))
excludes = append(excludes, itemDir)
}
} else {
excludes = append(excludes, loadPanelExcludes(item.Children)...)

View file

@ -27,6 +27,7 @@ func InitAgentDB() {
migrations.UpdateRuntime,
migrations.AddSnapshotRule,
migrations.UpdatePHPRuntime,
migrations.AddSnapshotIgnore,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -185,9 +185,6 @@ var InitSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "FileRecycleBin", Value: constant.StatusEnable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SnapshotIgnore", Value: "*.sock"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "LocalSSHConn", Value: ""}).Error; err != nil {
return err
@ -332,7 +329,6 @@ var AddSnapshotRule = &gormigrate.Migration{
)
},
}
var UpdatePHPRuntime = &gormigrate.Migration{
ID: "20250624-update-php-runtime",
Migrate: func(tx *gorm.DB) error {
@ -340,3 +336,11 @@ var UpdatePHPRuntime = &gormigrate.Migration{
return nil
},
}
var AddSnapshotIgnore = &gormigrate.Migration{
ID: "20250627-add-snapshot-ignore",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.Snapshot{},
)
},
}

View file

@ -815,6 +815,9 @@ func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules
if len(exclude) == 0 {
continue
}
if strings.HasPrefix(exclude, "/") {
exclude, _ = filepath.Rel(src, exclude)
}
if _, ok := exMap[exclude]; ok {
continue
}
@ -822,10 +825,6 @@ func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules
exMap[exclude] = struct{}{}
}
itemPrefix := filepath.Base(src)
if itemPrefix == "/" {
itemPrefix = ""
}
if len(secret) != 0 {
commands = fmt.Sprintf("tar %s -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", exStr, srcItem, secret, dst)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))

View file

@ -23,6 +23,7 @@ export namespace Cronjob {
appID: string;
website: string;
exclusionRules: string;
ignoreFiles: Array<string>;
dbType: string;
dbName: string;
url: string;

View file

@ -1,6 +1,5 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('setting.ignoreRule')" @close="handleClose" size="small">
<el-alert :closable="false" type="warning">{{ $t('setting.ignoreHelper') }}</el-alert>
<div>
<el-form ref="formRef" :model="form" :rules="rules" v-loading="loading" class="mt-2">
<el-form-item prop="tmpRule">
<div class="w-full">
@ -13,6 +12,7 @@
/>
<FileList @choose="loadDir" :path="baseDir" :isAll="true"></FileList>
</div>
<span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
</el-form-item>
</el-form>
@ -30,26 +30,26 @@
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave()">
{{ $t('commons.button.save') }}
</el-button>
</template>
</DrawerPro>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import FileList from '@/components/file-list/index.vue';
import { FormInstance } from 'element-plus';
import { getAgentSettingInfo, loadBaseDir, updateAgentSetting } from '@/api/modules/setting';
import { loadBaseDir } from '@/api/modules/setting';
const loading = ref();
const baseDir = ref();
const drawerVisible = ref(false);
const tableList = ref();
const em = defineEmits(['update:files']);
const props = defineProps({
files: {
type: Array<String>,
default: [],
},
});
const form = reactive({
tmpRule: '',
@ -76,17 +76,6 @@ function checkData(rule: any, value: any, callback: any) {
callback();
}
const acceptParams = async (): Promise<void> => {
loadPath();
const res = await getAgentSettingInfo();
tableList.value = [];
let items = res.data.snapshotIgnore.split(',');
for (const item of items) {
tableList.value.push({ value: item });
}
drawerVisible.value = true;
};
const loadPath = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
@ -106,35 +95,25 @@ const handleAdd = (formEl: FormInstance | undefined) => {
tableList.value.push({ value: item });
}
}
em(
'update:files',
tableList.value.map((item) => item.value),
);
form.tmpRule = '';
});
};
const handleDelete = (index: number) => {
tableList.value.splice(index, 1);
em(
'update:files',
tableList.value.map((item) => item.value),
);
};
const onSave = async () => {
let list = [];
for (const item of tableList.value) {
list.push(item.value);
}
await updateAgentSetting({ key: 'SnapshotIgnore', value: list.join(',') })
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
loading.value = false;
drawerVisible.value = false;
return;
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
onMounted(() => {
loadPath();
tableList.value = props.files.map((item) => {
return { value: item };
});
});
</script>

View file

@ -601,11 +601,7 @@
<el-row :gutter="20">
<LayoutCol :span="20" v-if="hasExclusionRules()">
<el-form-item :label="$t('cronjob.exclusionRules')" prop="exclusionRules">
<el-input
:placeholder="$t('cronjob.rulesHelper')"
clearable
v-model="form.exclusionRules"
/>
<IgnoreFile class="w-full" v-model:files="form.ignoreFiles"></IgnoreFile>
<span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
</el-form-item>
</LayoutCol>
@ -703,6 +699,7 @@ import { ElForm } from 'element-plus';
import { Cronjob } from '@/api/interface/cronjob';
import { addCronjob, editCronjob, loadCronjobInfo, loadNextHandle, loadScriptOptions } from '@/api/modules/cronjob';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import IgnoreFile from '@/components/file-batch/index.vue';
import LayoutCol from '@/components/layout-col/form.vue';
import { listDbItems } from '@/api/modules/database';
import { getWebsiteOptions } from '@/api/modules/website';
@ -756,6 +753,7 @@ const form = reactive<Cronjob.CronjobInfo>({
scriptID: null,
appID: '',
website: '',
ignoreFiles: [],
exclusionRules: '',
dbType: 'mysql',
dbName: '',
@ -835,6 +833,7 @@ const search = async () => {
form.website = res.data.website;
form.websiteList = res.data.website.split(',') || [];
form.exclusionRules = res.data.exclusionRules;
form.ignoreFiles = res.data.exclusionRules.split(',');
form.dbType = res.data.dbType;
form.dbName = res.data.dbName;
form.dbNameList = res.data.dbName.split(',') || [];
@ -1280,7 +1279,12 @@ function isBackup() {
}
function hasExclusionRules() {
return form.type === 'app' || form.type === 'website' || (form.type === 'directory' && form.isDir);
return (
form.type === 'app' ||
form.type === 'website' ||
form.type === 'snapshot' ||
(form.type === 'directory' && form.isDir)
);
}
function hasScript() {
@ -1327,6 +1331,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
form.dbName = form.dbNameList.join(',');
}
form.exclusionRules = form.ignoreFiles.join(',');
form.snapshotRule = { withImage: form.withImage, ignoreAppIDs: form.ignoreAppIDs };
form.alertCount = form.hasAlert && isProductPro.value ? form.alertCount : 0;
form.alertTitle =

View file

@ -143,11 +143,14 @@
</div>
</fu-step>
<template #footer></template>
<fu-step id="ignoreFiles" :title="$t('cronjob.exclusionRules')">
<IgnoreFile v-model:files="form.ignoreFiles"></IgnoreFile>
</fu-step>
</fu-steps>
<template #footer>
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button @click="prev" v-if="nowIndex !== 0">{{ $t('commons.button.prev') }}</el-button>
<el-button type="primary" v-if="nowIndex === 4" :disabled="loading" @click="submitAddSnapshot">
<el-button type="primary" v-if="nowIndex === 5" :disabled="loading" @click="submitAddSnapshot">
{{ $t('commons.button.create') }}
</el-button>
<el-button @click="next" v-else>{{ $t('commons.button.next') }}</el-button>
@ -161,6 +164,7 @@ import { loadSnapshotInfo, snapshotCreate } from '@/api/modules/setting';
import { computeSize, newUUID } from '@/utils/util';
import i18n from '@/lang';
import TaskLog from '@/components/log/task/index.vue';
import IgnoreFile from '@/components/file-batch/index.vue';
import { listBackupOptions } from '@/api/modules/backup';
import { Rules } from '@/global/form-rules';
import { ElForm } from 'element-plus';
@ -200,6 +204,7 @@ const form = reactive({
panelData: [],
backupData: [],
appData: [],
ignoreFiles: [],
});
const rules = reactive({
fromAccounts: [Rules.requiredSelect],
@ -272,6 +277,7 @@ const beforeLeave = async (stepItem: any) => {
}
return true;
}
return true;
};
function next() {

View file

@ -8,9 +8,6 @@
<el-button type="primary" plain @click="onImport()">
{{ $t('setting.importSnapshot') }}
</el-button>
<el-button type="primary" plain @click="onIgnore()">
{{ $t('setting.editIgnoreRule') }}
</el-button>
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
@ -205,7 +202,6 @@
<RecoverStatus ref="recoverStatusRef" @search="search()"></RecoverStatus>
<SnapshotCreate ref="createRef" @search="search()" />
<SnapshotImport ref="importRef" @search="search()" />
<IgnoreRule ref="ignoreRef" @search="search()" />
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()">
<template #content>
@ -220,7 +216,7 @@
</template>
</OpDialog>
<TaskLog ref="taskLogRef" width="70%" @close="search()" />
<SnapRecover ref="recoverRef" />
<SnapRecover ref="recoverRef" @search="search" />
</div>
</template>
@ -235,7 +231,6 @@ import {
import { onMounted, reactive, ref } from 'vue';
import { computeSize, dateFormat, newUUID } from '@/utils/util';
import { ElForm } from 'element-plus';
import IgnoreRule from '@/views/setting/snapshot/ignore-rule/index.vue';
import i18n from '@/lang';
import { Setting } from '@/api/interface/setting';
import TaskLog from '@/components/log/task/index.vue';
@ -263,7 +258,6 @@ const searchName = ref();
const opRef = ref();
const createRef = ref();
const ignoreRef = ref();
const recoverStatusRef = ref();
const importRef = ref();
const isRecordShow = ref();
@ -335,10 +329,6 @@ const reRollback = (row: any) => {
});
};
const onIgnore = () => {
ignoreRef.value.acceptParams();
};
const onChange = async (info: any) => {
await updateSnapshotDescription({ id: info.id, description: info.description });
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));

View file

@ -131,6 +131,7 @@ import { snapshotRollback } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
import { loadOsInfo } from '@/api/modules/dashboard';
import SnapRecover from '@/views/setting/snapshot/recover/index.vue';
import { newUUID } from '@/utils/util';
const drawerVisible = ref(false);
const snapInfo = ref();
@ -181,7 +182,8 @@ const rollbackSnapshot = async () => {
type: 'info',
}).then(async () => {
loading.value = true;
await snapshotRollback({ id: snapInfo.value.id, isNew: false, reDownload: false, secret: '' })
let taskID = newUUID();
await snapshotRollback({ id: snapInfo.value.id, isNew: false, reDownload: false, secret: '', taskID: taskID })
.then(() => {
emit('search');
loading.value = false;