feat: 完成网站恢复功能

pull/55/head
ssongliu 2022-11-30 10:34:44 +08:00 committed by ssongliu
parent 7b21bcbe7f
commit f50656320b
8 changed files with 148 additions and 7 deletions

View File

@ -4,6 +4,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
@ -51,6 +52,24 @@ func (b *BaseApi) BackupWebsite(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) RecoverWebsite(c *gin.Context) {
var req dto.WebSiteRecover
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.Recover(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) DeleteWebSite(c *gin.Context) {
var req dto.WebSiteDel
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -51,6 +51,12 @@ type WebSiteDel struct {
DeleteBackup bool `json:"deleteBackup"`
}
type WebSiteRecover struct {
WebsiteName string `json:"websiteName" validate:"required"`
Type string `json:"type" validate:"required"`
BackupName string `json:"backupName" validate:"required"`
}
type WebSiteDTO struct {
model.WebSite
}

View File

@ -2,6 +2,7 @@ package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
@ -16,6 +17,12 @@ func (w WebSiteRepo) WithAppInstallId(appInstallId uint) DBOption {
}
}
func (w WebSiteRepo) WithByDomain(domain string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("primary_domain = ?", domain)
}
}
func (w WebSiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSite, error) {
var websites []model.WebSite
db := getDb(opts...).Model(&model.WebSite{})

View File

@ -17,6 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
"gorm.io/gorm"
@ -185,6 +186,7 @@ func (w WebsiteService) Backup(id uint) error {
if err != nil {
return errors.New(string(stdout))
}
_ = os.RemoveAll(fullDir)
record := &model.BackupRecord{
Type: "website-" + website.Type,
@ -201,6 +203,93 @@ func (w WebsiteService) Backup(id uint) error {
return nil
}
func (w WebsiteService) Recover(req dto.WebSiteRecover) error {
website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName))
if err != nil {
return err
}
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
if !strings.Contains(req.BackupName, "/") {
return errors.New("error path of request")
}
fileDir := req.BackupName[:strings.LastIndex(req.BackupName, "/")]
fileName := strings.ReplaceAll(req.BackupName[strings.LastIndex(req.BackupName, "/"):], ".tar.gz", "")
if err := handleUnTar(req.BackupName, fileDir); err != nil {
return err
}
fileDir = fileDir + "/" + fileName
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
if err != nil {
return err
}
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
if err != nil {
return err
}
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
src, err := os.OpenFile(fmt.Sprintf("%s/%s.conf", fileDir, website.PrimaryDomain), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
return err
}
defer src.Close()
var out *os.File
nginxConfDir := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
if _, err := os.Stat(nginxConfDir); err != nil {
out, err = os.Create(fmt.Sprintf("%s/%s.conf", nginxConfDir, website.PrimaryDomain))
if err != nil {
return err
}
} else {
out, err = os.OpenFile(nginxConfDir, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
}
defer out.Close()
_, _ = io.Copy(out, src)
if website.Type == "deployment" {
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, db.Name)
sql, err := os.OpenFile(fmt.Sprintf("%s/%s.sql", fileDir, website.PrimaryDomain), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
cmd.Stdin = sql
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
appDir := fmt.Sprintf("%s/%s", constant.AppInstallDir, app.App.Key)
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/%s/docker-compose.yml", appDir, app.Name)); err != nil {
return err
}
} else {
appDir := fmt.Sprintf("%s/nginx/%s/www", constant.AppInstallDir, nginxInfo.Name)
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
return err
}
}
cmd := exec.Command("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload")
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func (w WebsiteService) UpdateWebsite(req dto.WebSiteUpdate) error {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {

View File

@ -17,6 +17,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
{
groupRouter.POST("", baseApi.CreateWebsite)
groupRouter.POST("/backup/:id", baseApi.BackupWebsite)
groupRouter.POST("/recover", baseApi.RecoverWebsite)
groupRouter.POST("/update", baseApi.UpdateWebSite)
groupRouter.GET("/:id", baseApi.GetWebSite)
groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx)

View File

@ -38,6 +38,12 @@ export namespace WebSite {
name: string;
}
export interface WebSiteRecover {
websiteName: string;
type: string;
backupName: string;
}
export interface WebSiteDel {
id: number;
deleteApp: boolean;

View File

@ -14,6 +14,9 @@ export const CreateWebsite = (req: WebSite.WebSiteCreateReq) => {
export const BackupWebsite = (id: number) => {
return http.post(`/websites/backup/${id}`);
};
export const RecoverWebsite = (req: WebSite.WebSiteRecover) => {
return http.post(`/websites/recover`, req);
};
export const UpdateWebsite = (req: WebSite.WebSiteUpdateReq) => {
return http.post<any>(`/websites/update`, req);

View File

@ -40,7 +40,7 @@ import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import { deleteBackupRecord, downloadBackupRecord, searchBackupRecords } from '@/api/modules/backup';
import { Backup } from '@/api/interface/backup';
import { BackupWebsite } from '@/api/modules/website';
import { BackupWebsite, RecoverWebsite } from '@/api/modules/website';
const selects = ref<any>([]);
@ -82,6 +82,16 @@ const search = async () => {
paginationConfig.total = res.data.total;
};
const onRecover = async (row: Backup.RecordInfo) => {
let params = {
websiteName: websiteName.value,
type: websiteType.value,
backupName: row.fileDir + '/' + row.fileName,
};
await RecoverWebsite(params);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
};
const onBackup = async () => {
await BackupWebsite(websiteID.value);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
@ -124,12 +134,12 @@ const buttons = [
onBatchDelete(row);
},
},
// {
// label: i18n.global.t('commons.button.recover'),
// click: (row: Backup.RecordInfo) => {
// onRecover(row);
// },
// },
{
label: i18n.global.t('commons.button.recover'),
click: (row: Backup.RecordInfo) => {
onRecover(row);
},
},
{
label: i18n.global.t('commons.button.download'),
click: (row: Backup.RecordInfo) => {