fix: 限制快照跨架构恢复 (#4064)

Refs #4056
pull/4067/head
ssongliu 9 months ago committed by GitHub
parent c49c4b7863
commit 39b8de7ada
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -8,6 +8,22 @@ import (
"github.com/gin-gonic/gin"
)
// @Tags Dashboard
// @Summary Load os info
// @Description 获取服务器基础数据
// @Accept json
// @Success 200 {object} dto.OsInfo
// @Security ApiKeyAuth
// @Router /dashboard/base/os [get]
func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
data, err := dashboardService.LoadOsInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary Load dashboard base info
// @Description 获取首页基础数据

@ -24,6 +24,14 @@ type DashboardBase struct {
CurrentInfo DashboardCurrent `json:"currentInfo"`
}
type OsInfo struct {
OS string `json:"os"`
Platform string `json:"platform"`
PlatformFamily string `json:"platformFamily"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
}
type DashboardCurrent struct {
Uptime uint64 `json:"uptime"`
TimeSinceUptime string `json:"timeSinceUptime"`

@ -22,6 +22,7 @@ import (
type DashboardService struct{}
type IDashboardService interface {
LoadOsInfo() (*dto.OsInfo, error)
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
@ -49,6 +50,27 @@ func (u *DashboardService) Restart(operation string) error {
return nil
}
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
var baseInfo dto.OsInfo
hostInfo, err := host.Info()
if err != nil {
return nil, err
}
baseInfo.OS = hostInfo.OS
baseInfo.Platform = hostInfo.Platform
baseInfo.PlatformFamily = hostInfo.PlatformFamily
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
if baseInfo.KernelArch == "armv7l" {
baseInfo.KernelArch = "armv7"
}
if baseInfo.KernelArch == "x86_64" {
baseInfo.KernelArch = "amd64"
}
return &baseInfo, nil
}
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
var baseInfo dto.DashboardBase
hostInfo, err := host.Info()

@ -19,6 +19,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/v3/host"
)
type SnapshotService struct {
@ -134,6 +135,9 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
if err != nil {
return err
}
if hasOs(snap.Name) && !strings.Contains(snap.Name, loadOs()) {
return fmt.Errorf("Restoring snapshots(%s) between different server architectures(%s) is not supported.", snap.Name, loadOs())
}
if !req.IsNew && len(snap.InterruptStep) != 0 && len(snap.RollbackStatus) != 0 {
return fmt.Errorf("the snapshot has been rolled back and cannot be restored again")
}
@ -189,9 +193,10 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
if req.ID == 0 {
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
name := fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow)
name := fmt.Sprintf("1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
if isCronjob {
name = fmt.Sprintf("snapshot_1panel_%s_%s", versionItem.Value, timeNow)
name = fmt.Sprintf("snapshot_1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
}
rootDir = path.Join(localDir, "system", name)
@ -481,3 +486,23 @@ func loadLogByStatus(status model.SnapshotStatus, logPath string) {
defer file.Close()
_, _ = file.Write([]byte(logs))
}
func hasOs(name string) bool {
return strings.Contains(name, "amd64") ||
strings.Contains(name, "arm64") ||
strings.Contains(name, "armv7") ||
strings.Contains(name, "ppc64le") ||
strings.Contains(name, "s390x")
}
func loadOs() string {
hostInfo, _ := host.Info()
switch hostInfo.KernelArch {
case "x86_64":
return "amd64"
case "armv7l":
return "armv7"
default:
return hostInfo.KernelArch
}
}

@ -3830,6 +3830,31 @@ const docTemplate = `{
}
}
},
"/dashboard/base/os": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取服务器基础数据",
"consumes": [
"application/json"
],
"tags": [
"Dashboard"
],
"summary": "Load os info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.OsInfo"
}
}
}
}
},
"/dashboard/current/:ioOption/:netOption": {
"get": {
"security": [
@ -16828,6 +16853,26 @@ const docTemplate = `{
}
}
},
"dto.OsInfo": {
"type": "object",
"properties": {
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"os": {
"type": "string"
},
"platform": {
"type": "string"
},
"platformFamily": {
"type": "string"
}
}
},
"dto.PageContainer": {
"type": "object",
"required": [
@ -18967,7 +19012,6 @@ const docTemplate = `{
"request.FileEdit": {
"type": "object",
"required": [
"content",
"path"
],
"properties": {

@ -3823,6 +3823,31 @@
}
}
},
"/dashboard/base/os": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取服务器基础数据",
"consumes": [
"application/json"
],
"tags": [
"Dashboard"
],
"summary": "Load os info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.OsInfo"
}
}
}
}
},
"/dashboard/current/:ioOption/:netOption": {
"get": {
"security": [
@ -16821,6 +16846,26 @@
}
}
},
"dto.OsInfo": {
"type": "object",
"properties": {
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"os": {
"type": "string"
},
"platform": {
"type": "string"
},
"platformFamily": {
"type": "string"
}
}
},
"dto.PageContainer": {
"type": "object",
"required": [
@ -18960,7 +19005,6 @@
"request.FileEdit": {
"type": "object",
"required": [
"content",
"path"
],
"properties": {

@ -1887,6 +1887,19 @@ definitions:
option:
type: string
type: object
dto.OsInfo:
properties:
kernelArch:
type: string
kernelVersion:
type: string
os:
type: string
platform:
type: string
platformFamily:
type: string
type: object
dto.PageContainer:
properties:
excludeAppStore:
@ -3319,7 +3332,6 @@ definitions:
path:
type: string
required:
- content
- path
type: object
request.FileMove:
@ -7412,6 +7424,21 @@ paths:
summary: Load dashboard base info
tags:
- Dashboard
/dashboard/base/os:
get:
consumes:
- application/json
description: 获取服务器基础数据
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.OsInfo'
security:
- ApiKeyAuth: []
summary: Load os info
tags:
- Dashboard
/dashboard/current/:ioOption/:netOption:
get:
consumes:

@ -1,4 +1,11 @@
export namespace Dashboard {
export interface OsInfo {
os: string;
platform: string;
platformFamily: string;
kernelArch: string;
kernelVersion: string;
}
export interface BaseInfo {
websiteNumber: number;
databaseNumber: number;

@ -1,6 +1,10 @@
import http from '@/api';
import { Dashboard } from '../interface/dashboard';
export const loadOsInfo = () => {
return http.get<Dashboard.OsInfo>(`/dashboard/base/os`);
};
export const loadBaseInfo = (ioOption: string, netOption: string) => {
return http.get<Dashboard.BaseInfo>(`/dashboard/base/${ioOption}/${netOption}`);
};

@ -1405,14 +1405,15 @@ const message = {
createSnapshot: 'Create snapshot',
importSnapshot: 'Sync snapshot',
recover: 'Recover',
noRecoverRecord: 'No recovery record has been recorded',
lastRecoverAt: 'Last recovery time',
lastRollbackAt: 'Last rollback time',
noRollbackRecord: 'No rollback record has been recorded',
reDownload: 'Download the backup file again',
recoverRecord: 'Recover record',
recoverHelper:
'The recovery is about to start from snapshot {0}, and the recovery needs to restart docker and 1panel service, do you want to continue?',
recoverHelper1:
'Will start restoring from snapshot {0}, please ensure that the server architecture matches the one where the snapshot was created.',
recoverHelper2: 'Restoring snapshots between different server architectures is not supported.',
rollback: 'Rollback',
rollbackHelper:
'This recovery is about to be rolled back, which will replace all the files recovered this time. In the process, docker and 1panel services may need to be restarted. Do you want to continue?',

@ -1246,17 +1246,14 @@ const message = {
createSnapshot: '',
importSnapshot: '',
recover: '',
noRecoverRecord: '',
lastRecoverAt: '',
lastRollbackAt: '',
noRollbackRecord: '',
reDownload: '',
statusAll: '',
statusSuccess: '',
statusFailed: '',
versionChange: '',
snapshotFrom: '',
recoverHelper: ' {0} docker 1panel ',
recoverHelper1: ' {0} ',
recoverHelper2: '',
rollback: '',
rollbackHelper:
' docker 1panel ',

@ -1247,17 +1247,14 @@ const message = {
createSnapshot: '',
importSnapshot: '',
recover: '',
noRecoverRecord: '',
lastRecoverAt: '',
lastRollbackAt: '',
noRollbackRecord: '',
reDownload: '',
statusAll: '',
statusSuccess: '',
statusFailed: '',
versionChange: '',
snapshotFrom: '',
recoverHelper: ' {0} docker 1panel ',
recoverHelper1: ' {0} ',
recoverHelper2: '',
rollback: '',
rollbackHelper:
' docker 1panel ',

@ -172,7 +172,8 @@ import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { snapshotRecover, snapshotRollback } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
import { MsgError, MsgSuccess } from '@/utils/message';
import { loadOsInfo } from '@/api/modules/dashboard';
const drawerVisible = ref(false);
const snapInfo = ref();
@ -210,7 +211,27 @@ const doRecover = async (isNew: boolean) => {
};
const recoverSnapshot = async (isNew: boolean) => {
ElMessageBox.confirm(i18n.global.t('setting.recoverHelper', [snapInfo.value.name]), {
let msg = i18n.global.t('setting.recoverHelper', [snapInfo.value.name]);
if (
snapInfo.value.name.indexOf('amd64') === -1 &&
snapInfo.value.name.indexOf('arm64') === -1 &&
snapInfo.value.name.indexOf('armv7') === -1 &&
snapInfo.value.name.indexOf('ppc64le') === -1 &&
snapInfo.value.name.indexOf('s390x') === -1
) {
msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]);
} else {
const res = await loadOsInfo();
let osVal = res.data.kernelArch;
if (osVal === '') {
msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]);
} else if (snapInfo.value.name.indexOf(osVal) === -1) {
MsgError(i18n.global.t('setting.recoverHelper2'));
return;
}
}
ElMessageBox.confirm(msg, i18n.global.t('commons.button.recover'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',

Loading…
Cancel
Save