Browse Source

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

Refs #4056
pull/4067/head
ssongliu 9 months ago committed by GitHub
parent
commit
39b8de7ada
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 16
      backend/app/api/v1/dashboard.go
  2. 8
      backend/app/dto/dashboard.go
  3. 22
      backend/app/service/dashboard.go
  4. 29
      backend/app/service/snapshot.go
  5. 46
      cmd/server/docs/docs.go
  6. 46
      cmd/server/docs/swagger.json
  7. 29
      cmd/server/docs/swagger.yaml
  8. 7
      frontend/src/api/interface/dashboard.ts
  9. 4
      frontend/src/api/modules/dashboard.ts
  10. 5
      frontend/src/lang/modules/en.ts
  11. 7
      frontend/src/lang/modules/tw.ts
  12. 7
      frontend/src/lang/modules/zh.ts
  13. 25
      frontend/src/views/setting/snapshot/status/index.vue

16
backend/app/api/v1/dashboard.go

@ -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 获取首页基础数据

8
backend/app/dto/dashboard.go

@ -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
backend/app/service/dashboard.go

@ -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()

29
backend/app/service/snapshot.go

@ -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
}
}

46
cmd/server/docs/docs.go

@ -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": {

46
cmd/server/docs/swagger.json

@ -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": {

29
cmd/server/docs/swagger.yaml

@ -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:

7
frontend/src/api/interface/dashboard.ts

@ -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;

4
frontend/src/api/modules/dashboard.ts

@ -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}`);
};

5
frontend/src/lang/modules/en.ts

@ -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?',

7
frontend/src/lang/modules/tw.ts

@ -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 服務是否繼續',

7
frontend/src/lang/modules/zh.ts

@ -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 服务是否继续',

25
frontend/src/views/setting/snapshot/status/index.vue

@ -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