mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 00:00:32 +00:00
Compare commits
66
Commits
v1.9.6
...
v1.10.1-lts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe717d993b | ||
|
|
703da8a09a | ||
|
|
a99f19471b | ||
|
|
ca3be03ae7 | ||
|
|
71ca8016e1 | ||
|
|
5633bdd41e | ||
|
|
ab11d7257c | ||
|
|
39b8de7ada | ||
|
|
c49c4b7863 | ||
|
|
d42df7184c | ||
|
|
e7f172520a | ||
|
|
dca29174ea | ||
|
|
94181538a9 | ||
|
|
e9cc7bf1cf | ||
|
|
fa1e945b8d | ||
|
|
94702a79a4 | ||
|
|
24f573f9fd | ||
|
|
7e4606eda5 | ||
|
|
73e118a3e5 | ||
|
|
514d68d395 | ||
|
|
d0bb07796b | ||
|
|
66a451fc1f | ||
|
|
c13e2a36cc | ||
|
|
2affd2bc79 | ||
|
|
c510a028db | ||
|
|
121a1ff5da | ||
|
|
1ecc53dad3 | ||
|
|
279b3942b4 | ||
|
|
a0af04bbf7 | ||
|
|
d5d1125660 | ||
|
|
08801ebfcd | ||
|
|
262cbb9405 | ||
|
|
96233ee9b2 | ||
|
|
49dcb094dc | ||
|
|
f0d5aca657 | ||
|
|
58f58f0fac | ||
|
|
3bd6f46fe2 | ||
|
|
e427ccf70c | ||
|
|
1b47e8dd63 | ||
|
|
b668f55222 | ||
|
|
9a127d9afe | ||
|
|
63484601b5 | ||
|
|
ef5e42cf0b | ||
|
|
50a83e7e11 | ||
|
|
2475c82a63 | ||
|
|
61ff55e157 | ||
|
|
dbc7799e89 | ||
|
|
6275e06806 | ||
|
|
e4f9ec32cd | ||
|
|
319c21fc36 | ||
|
|
e346442d36 | ||
|
|
1781362342 | ||
|
|
3035bb2bf6 | ||
|
|
b896a5be3b | ||
|
|
d51abd529a | ||
|
|
550eed7962 | ||
|
|
51d09b7164 | ||
|
|
046d8f526b | ||
|
|
5bf121b687 | ||
|
|
e6a43d22c8 | ||
|
|
3fd7aa17a8 | ||
|
|
6a903b7fdf | ||
|
|
958f9a1aad | ||
|
|
e48830cd99 | ||
|
|
ece2cb433d | ||
|
|
fb06e29aa3 |
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18.14'
|
||||
node-version: '20.2'
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.x'
|
||||
go-version: '1.21.x'
|
||||
- name: Build Release
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
|
||||
@@ -39,6 +39,13 @@ frontend/auto-imports.d.ts
|
||||
frontend/components.d.ts
|
||||
frontend/src/xpack
|
||||
backend/xpack
|
||||
backend/router/entry_xpack.go
|
||||
backend/server/init_xpack.go
|
||||
plugins/openresty/waf/db
|
||||
plugins/openresty/waf/data
|
||||
plugins/openresty/waf/sites
|
||||
plugins/openresty/waf/conf/websites.json
|
||||
|
||||
|
||||
.history/
|
||||
dist/
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
------------------------------
|
||||
|
||||
1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括:
|
||||
1Panel 是新一代的 Linux 服务器运维管理面板。
|
||||
|
||||
- **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定;
|
||||
- **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等;
|
||||
- **安全可靠**:基于容器来管理和部署应用,最小漏洞暴露面,提供防火墙和日志审计等功能;
|
||||
- **一键备份**:支持一键备份和恢复,备份数据到各类云端存储,永不丢失;
|
||||
- **应用商店**:精选各类高质量开源工具和应用软件,助力您轻松安装和升级。
|
||||
- **高效管理**:用户可以通过 Web 图形界面轻松管理 Linux 服务器,实现主机监控、文件管理、数据库管理、容器管理等功能;
|
||||
- **快速建站**:深度集成开源建站软件 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等操作一键搞定;
|
||||
- **应用商店**:精选上架各类高质量的开源工具和应用软件,协助用户轻松安装并升级;
|
||||
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供防火墙和日志审计等功能;
|
||||
- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。
|
||||
|
||||
## UI 展示
|
||||
|
||||
|
||||
@@ -101,11 +101,7 @@ func (b *BaseApi) LoadRecordLog(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
content, err := cronjobService.LoadRecordLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
content := cronjobService.LoadRecordLog(req)
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 获取首页基础数据
|
||||
|
||||
@@ -164,24 +164,6 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary List postgresql database names
|
||||
// @Description 获取 postgresql 数据库列表
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Success 200 {array} dto.PostgresqlOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/pg/options [get]
|
||||
func (b *BaseApi) ListPostgresqlDBName(c *gin.Context) {
|
||||
list, err := postgresqlService.ListDBOption()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Database Postgresql
|
||||
// @Summary Load postgresql database from remote
|
||||
// @Description 从服务器获取
|
||||
|
||||
@@ -38,8 +38,8 @@ type ContainerInfo struct {
|
||||
}
|
||||
|
||||
type ResourceLimit struct {
|
||||
CPU int `json:"cpu"`
|
||||
Memory int `json:"memory"`
|
||||
CPU int `json:"cpu"`
|
||||
Memory uint64 `json:"memory"`
|
||||
}
|
||||
|
||||
type ContainerOperate struct {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -64,7 +64,7 @@ type FileDeCompress struct {
|
||||
|
||||
type FileEdit struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type FileRename struct {
|
||||
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
@@ -152,7 +150,7 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
|
||||
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
||||
return res, nil
|
||||
}
|
||||
if err = syncByID(appInstall.ID); err != nil {
|
||||
if err = syncAppInstallStatus(&appInstall); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -242,26 +240,26 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
if err != nil {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Stop:
|
||||
out, err := compose.Stop(dockerComposePath)
|
||||
if err != nil {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Restart:
|
||||
out, err := compose.Restart(dockerComposePath)
|
||||
if err != nil {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Delete:
|
||||
if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case constant.Sync:
|
||||
return syncByID(install.ID)
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Upgrade:
|
||||
return upgradeInstall(install.ID, req.DetailId, req.Backup, req.PullImage)
|
||||
case constant.Reload:
|
||||
@@ -413,7 +411,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
return err
|
||||
}
|
||||
for _, i := range allList {
|
||||
if i.Status == constant.Installing || i.Status == constant.Upgrading {
|
||||
if i.Status == constant.Installing || i.Status == constant.Upgrading || i.Status == constant.Rebuilding {
|
||||
if systemInit {
|
||||
i.Status = constant.Error
|
||||
i.Message = "1Panel restart causes the task to terminate"
|
||||
@@ -422,7 +420,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
continue
|
||||
}
|
||||
if !systemInit {
|
||||
if err := syncByID(i.ID); err != nil {
|
||||
if err = syncAppInstallStatus(&i); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
}
|
||||
}
|
||||
@@ -529,7 +527,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updateInstallInfoInDB(req.Key, req.Name, "port", true, strconv.FormatInt(req.Port, 10)); err != nil {
|
||||
if err := updateInstallInfoInDB(req.Key, req.Name, "port", strconv.FormatInt(req.Port, 10)); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -641,10 +639,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
for _, form := range appForm.FormFields {
|
||||
if v, ok := envs[form.EnvKey]; ok {
|
||||
appParam := response.AppParam{
|
||||
Edit: false,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
Edit: false,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
Multiple: form.Multiple,
|
||||
}
|
||||
if form.Edit {
|
||||
appParam.Edit = true
|
||||
@@ -656,10 +655,20 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string)))
|
||||
appParam.ShowValue = appInstall.Name
|
||||
} else if form.Type == "select" {
|
||||
for _, fv := range form.Values {
|
||||
if fv.Value == v {
|
||||
appParam.ShowValue = fv.Label
|
||||
break
|
||||
if form.Multiple {
|
||||
if v == "" {
|
||||
appParam.Value = []string{}
|
||||
} else {
|
||||
if str, ok := v.(string); ok {
|
||||
appParam.Value = strings.Split(str, ",")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, fv := range form.Values {
|
||||
if fv.Value == v {
|
||||
appParam.ShowValue = fv.Label
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
appParam.Values = form.Values
|
||||
@@ -667,14 +676,15 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
params = append(params, appParam)
|
||||
} else {
|
||||
params = append(params, response.AppParam{
|
||||
Edit: form.Edit,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
LabelZh: form.LabelZh,
|
||||
LabelEn: form.LabelEn,
|
||||
Value: form.Default,
|
||||
Values: form.Values,
|
||||
Edit: form.Edit,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
LabelZh: form.LabelZh,
|
||||
LabelEn: form.LabelEn,
|
||||
Value: form.Default,
|
||||
Values: form.Values,
|
||||
Multiple: form.Multiple,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -690,15 +700,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func syncByID(installID uint) error {
|
||||
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if appInstall.Status == constant.Installing {
|
||||
func syncAppInstallStatus(appInstall *model.AppInstall) error {
|
||||
if appInstall.Status == constant.Installing || appInstall.Status == constant.Rebuilding || appInstall.Status == constant.Upgrading {
|
||||
return nil
|
||||
}
|
||||
containerNames, err := getContainerNames(appInstall)
|
||||
containerNames, err := getContainerNames(*appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -710,75 +716,67 @@ func syncByID(installID uint) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
errorContainers []string
|
||||
notFoundContainers []string
|
||||
runningContainers []string
|
||||
exitedContainers []string
|
||||
runningCount int
|
||||
exitedCount int
|
||||
pausedCount int
|
||||
exitedContainerNames []string
|
||||
total = len(containerNames)
|
||||
count = len(containers)
|
||||
)
|
||||
|
||||
for _, n := range containers {
|
||||
switch n.State {
|
||||
foundNames := make(map[string]bool)
|
||||
for _, con := range containers {
|
||||
foundNames[con.Names[0]] = true
|
||||
switch con.State {
|
||||
case "exited":
|
||||
exitedContainers = append(exitedContainers, n.Names[0])
|
||||
exitedContainerNames = append(exitedContainerNames, strings.TrimPrefix(con.Names[0], "/"))
|
||||
exitedCount++
|
||||
case "running":
|
||||
runningContainers = append(runningContainers, n.Names[0])
|
||||
default:
|
||||
errorContainers = append(errorContainers, n.Names[0])
|
||||
runningCount++
|
||||
case "paused":
|
||||
pausedCount++
|
||||
}
|
||||
}
|
||||
|
||||
var notFoundNames []string
|
||||
for _, name := range containerNames {
|
||||
exist := false
|
||||
for _, container := range containers {
|
||||
if common.ExistWithStrArray(name, container.Names) {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
notFoundContainers = append(notFoundContainers, name)
|
||||
if !foundNames["/"+name] {
|
||||
notFoundNames = append(notFoundNames, name)
|
||||
}
|
||||
}
|
||||
|
||||
containerCount := len(containers)
|
||||
errCount := len(errorContainers)
|
||||
notFoundCount := len(notFoundContainers)
|
||||
existedCount := len(exitedContainers)
|
||||
normalCount := len(containerNames)
|
||||
runningCount := len(runningContainers)
|
||||
|
||||
if containerCount == 0 {
|
||||
switch {
|
||||
case count == 0:
|
||||
appInstall.Status = constant.Error
|
||||
appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")})
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
|
||||
appInstall.Status = constant.Running
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
if existedCount == normalCount {
|
||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||
case exitedCount == total:
|
||||
appInstall.Status = constant.Stopped
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}
|
||||
if errCount == normalCount {
|
||||
appInstall.Status = constant.Error
|
||||
}
|
||||
if runningCount < normalCount {
|
||||
case runningCount == total:
|
||||
appInstall.Status = constant.Running
|
||||
case pausedCount == total:
|
||||
appInstall.Status = constant.Paused
|
||||
default:
|
||||
var msg string
|
||||
if exitedCount > 0 {
|
||||
msg = buserr.WithName("ErrContainerMsg", strings.Join(exitedContainerNames, ",")).Error()
|
||||
}
|
||||
if len(notFoundNames) > 0 {
|
||||
msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error()
|
||||
}
|
||||
if msg == "" {
|
||||
msg = buserr.New("ErrAppWarn").Error()
|
||||
}
|
||||
appInstall.Message = msg
|
||||
appInstall.Status = constant.UnHealthy
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
|
||||
var errMsg string
|
||||
if errCount > 0 {
|
||||
errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
|
||||
}
|
||||
if notFoundCount > 0 {
|
||||
errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
|
||||
}
|
||||
appInstall.Message = errMsg
|
||||
return appInstallRepo.Save(context.Background(), &appInstall)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error {
|
||||
func updateInstallInfoInDB(appKey, appName, param string, value interface{}) error {
|
||||
if param != "password" && param != "port" && param != "user-password" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -354,8 +354,8 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
|
||||
websiteApp, _ := appRepo.GetFirst(commonRepo.WithByID(websiteAppInstall.AppId))
|
||||
if websiteApp.Type == constant.RuntimePHP {
|
||||
go func() {
|
||||
_, _ = compose.Down(install.GetComposePath())
|
||||
_ = op.DeleteDir(install.GetPath())
|
||||
_, _ = compose.Down(websiteAppInstall.GetComposePath())
|
||||
_ = op.DeleteDir(websiteAppInstall.GetPath())
|
||||
}()
|
||||
_ = appInstallRepo.Delete(ctx, websiteAppInstall)
|
||||
}
|
||||
@@ -687,6 +687,12 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
|
||||
envParams[k] = strconv.Itoa(int(t))
|
||||
case int:
|
||||
envParams[k] = strconv.Itoa(t)
|
||||
case []interface{}:
|
||||
strArray := make([]string, len(t))
|
||||
for i := range t {
|
||||
strArray[i] = strings.ToLower(fmt.Sprintf("%v", t[i]))
|
||||
}
|
||||
envParams[k] = strings.Join(strArray, ",")
|
||||
default:
|
||||
envParams[k] = t.(string)
|
||||
}
|
||||
@@ -843,38 +849,34 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
|
||||
func upApp(appInstall *model.AppInstall, pullImages bool) {
|
||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||
if err == nil {
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := upProject(appInstall); err != nil {
|
||||
appInstall.Status = constant.Error
|
||||
@@ -897,11 +899,12 @@ func rebuildApp(appInstall model.AppInstall) error {
|
||||
_ = handleErr(appInstall, err, out)
|
||||
return
|
||||
}
|
||||
out, err = compose.Up(dockerComposePath)
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
_ = handleErr(appInstall, err, out)
|
||||
return
|
||||
}
|
||||
|
||||
appInstall.Status = constant.Running
|
||||
_ = appInstallRepo.Save(context.Background(), &appInstall)
|
||||
}()
|
||||
@@ -989,6 +992,43 @@ func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
|
||||
if err != nil {
|
||||
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
|
||||
}
|
||||
var appParam dto.AppForm
|
||||
if err = json.Unmarshal(dataJson, &appParam); err != nil {
|
||||
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
|
||||
}
|
||||
for _, formField := range appParam.FormFields {
|
||||
if strings.Contains(formField.EnvKey, " ") {
|
||||
return buserr.WithName(constant.ErrAppParamKey, formField.EnvKey)
|
||||
}
|
||||
}
|
||||
|
||||
var dataMap map[string]interface{}
|
||||
err = yaml.Unmarshal(paramByte, &dataMap)
|
||||
if err != nil {
|
||||
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
|
||||
}
|
||||
|
||||
additionalProperties, ok := dataMap["additionalProperties"].(map[string]interface{})
|
||||
if !ok {
|
||||
return buserr.WithName(constant.ErrAppParamKey, "additionalProperties")
|
||||
}
|
||||
|
||||
formFieldsInterface, ok := additionalProperties["formFields"]
|
||||
if ok {
|
||||
formFields, ok := formFieldsInterface.([]interface{})
|
||||
if !ok {
|
||||
return buserr.WithName(constant.ErrAppParamKey, "formFields")
|
||||
}
|
||||
for _, item := range formFields {
|
||||
field := item.(map[string]interface{})
|
||||
for key, value := range field {
|
||||
if value == nil {
|
||||
return buserr.WithName(constant.ErrAppParamKey, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appDetail.Params = string(dataJson)
|
||||
return nil
|
||||
}
|
||||
@@ -1040,8 +1080,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
||||
if out != "" {
|
||||
install.Message = out
|
||||
reErr = errors.New(out)
|
||||
install.Status = constant.Error
|
||||
}
|
||||
install.Status = constant.Error
|
||||
_ = appInstallRepo.Save(context.Background(), &install)
|
||||
return reErr
|
||||
}
|
||||
@@ -1052,6 +1092,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
|
||||
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
|
||||
continue
|
||||
}
|
||||
if err := syncAppInstallStatus(&installed); err != nil {
|
||||
global.LOG.Error("sync app install status error : ", err)
|
||||
}
|
||||
|
||||
installDTO := response.AppInstalledDTO{
|
||||
AppInstall: installed,
|
||||
Path: installed.GetPath(),
|
||||
|
||||
@@ -133,7 +133,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
|
||||
sessionUser, err := global.SESSION.Get(sID)
|
||||
if err != nil {
|
||||
sID = uuid.New().String()
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", false)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Set(sID, sessionUser, lifeTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -154,7 +154,7 @@ func (u *AuthService) LogOut(c *gin.Context) error {
|
||||
}
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
if sID != "" {
|
||||
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", false)
|
||||
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", true)
|
||||
err := global.SESSION.Delete(sID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -268,11 +268,13 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
|
||||
for _, record := range records {
|
||||
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
|
||||
if err != nil {
|
||||
return err
|
||||
global.LOG.Errorf("load backup account %s info from db failed, err: %v", record.Source, err)
|
||||
continue
|
||||
}
|
||||
client, err := u.NewClient(&backupAccount)
|
||||
if err != nil {
|
||||
return err
|
||||
global.LOG.Errorf("new client for backup account %s failed, err: %v", record.Source, err)
|
||||
continue
|
||||
}
|
||||
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
|
||||
global.LOG.Errorf("remove file %s from %s failed, err: %v", path.Join(record.FileDir, record.FileName), record.Source, err)
|
||||
|
||||
@@ -115,7 +115,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
if err := handleMysqlBackup(db.MysqlName, resource.Key, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppPostgresql:
|
||||
|
||||
@@ -29,7 +29,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
targetDir := path.Join(localDir, itemDir)
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||
|
||||
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
|
||||
if err := handleMysqlBackup(req.Name, req.Type, req.DetailName, targetDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -100,18 +100,20 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMysqlBackup(database, dbName, targetDir, fileName string) error {
|
||||
func handleMysqlBackup(database, dbType, dbName, targetDir, fileName string) error {
|
||||
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(database))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadMysqlClientByFrom(database)
|
||||
cli, version, err := LoadMysqlClientByFrom(database)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backupInfo := client.BackupInfo{
|
||||
Name: dbName,
|
||||
Type: dbType,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: targetDir,
|
||||
FileName: fileName,
|
||||
@@ -134,7 +136,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadMysqlClientByFrom(req.Name)
|
||||
cli, version, err := LoadMysqlClientByFrom(req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,6 +145,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
|
||||
if err := cli.Backup(client.BackupInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: path.Dir(rollbackFile),
|
||||
FileName: path.Base(rollbackFile),
|
||||
@@ -156,6 +160,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: rollbackFile,
|
||||
|
||||
@@ -172,6 +178,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
}
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Type: req.Type,
|
||||
Version: version,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: req.File,
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -18,6 +17,8 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -336,7 +337,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
||||
|
||||
data := dto.ResourceLimit{
|
||||
CPU: cpuCounts,
|
||||
Memory: int(memoryInfo.Total),
|
||||
Memory: memoryInfo.Total,
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
@@ -434,7 +435,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
data.AutoRemove = oldContainer.HostConfig.AutoRemove
|
||||
data.Privileged = oldContainer.HostConfig.Privileged
|
||||
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
|
||||
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
|
||||
data.RestartPolicy = string(oldContainer.HostConfig.RestartPolicy.Name)
|
||||
if oldContainer.HostConfig.NanoCPUs != 0 {
|
||||
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
|
||||
}
|
||||
@@ -683,7 +684,6 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
|
||||
global.LOG.Errorf("read bytes from log failed, err: %v", err)
|
||||
continue
|
||||
}
|
||||
// check if the bytes is valid utf8
|
||||
if !utf8.Valid(buffer[:n]) {
|
||||
continue
|
||||
}
|
||||
@@ -723,7 +723,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
|
||||
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
|
||||
data.Cache = float64(cache) / 1024 / 1024
|
||||
}
|
||||
data.Memory = data.Memory - data.Cache
|
||||
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
|
||||
data.ShotTime = stats.Read
|
||||
return &data, nil
|
||||
@@ -791,7 +790,7 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
|
||||
}
|
||||
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
|
||||
memPercent := 0.0
|
||||
memUsage := float64(memStats.Usage - memStats.Stats["cache"])
|
||||
memUsage := float64(memStats.Usage)
|
||||
memLimit := float64(memStats.Limit)
|
||||
if memUsage > 0.0 && memLimit > 0.0 {
|
||||
memPercent = (memUsage / memLimit) * 100.0
|
||||
@@ -895,6 +894,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
|
||||
data.MemoryCache = stats.MemoryStats.Stats["cache"]
|
||||
data.MemoryUsage = stats.MemoryStats.Usage
|
||||
data.MemoryLimit = stats.MemoryStats.Limit
|
||||
|
||||
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
|
||||
return data
|
||||
}
|
||||
@@ -985,7 +985,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
|
||||
hostConf.AutoRemove = req.AutoRemove
|
||||
hostConf.CPUShares = req.CPUShares
|
||||
hostConf.PublishAllPorts = req.PublishAllPorts
|
||||
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
|
||||
hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)}
|
||||
if req.RestartPolicy == "on-failure" {
|
||||
hostConf.RestartPolicy.MaximumRetryCount = 5
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/jinzhu/copier"
|
||||
@@ -33,7 +32,7 @@ type ICronjobService interface {
|
||||
StartJob(cronjob *model.Cronjob) (string, error)
|
||||
CleanRecord(req dto.CronjobClean) error
|
||||
|
||||
LoadRecordLog(req dto.OperateByID) (string, error)
|
||||
LoadRecordLog(req dto.OperateByID) string
|
||||
}
|
||||
|
||||
func NewICronjobService() ICronjobService {
|
||||
@@ -77,19 +76,19 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
|
||||
return total, dtoCronjobs, err
|
||||
}
|
||||
|
||||
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
|
||||
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string {
|
||||
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return ""
|
||||
}
|
||||
if _, err := os.Stat(record.Records); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
return ""
|
||||
}
|
||||
content, err := os.ReadFile(record.Records)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return ""
|
||||
}
|
||||
return string(content), nil
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
|
||||
@@ -109,7 +109,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
|
||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
|
||||
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
|
||||
if err := handleMysqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
if err := handleMysqlBackup(dbInfo.Database, dbInfo.DBType, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -82,7 +82,10 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
|
||||
if len(message) != 0 {
|
||||
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
|
||||
}
|
||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
|
||||
return
|
||||
}
|
||||
if len(message) != 0 {
|
||||
|
||||
@@ -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()
|
||||
@@ -183,7 +205,10 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
var datas []dto.DiskInfo
|
||||
stdout, err := cmd.ExecWithTimeOut("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 2*time.Second)
|
||||
if err != nil {
|
||||
return datas
|
||||
stdout, err = cmd.ExecWithTimeOut("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 1*time.Second)
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
}
|
||||
|
||||
global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name)
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -393,7 +393,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
}
|
||||
|
||||
global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name)
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -406,7 +406,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -55,10 +55,10 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
|
||||
}
|
||||
|
||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||
if err := updateInstallInfoInDB("redis", "", "password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB("redis", "", "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateInstallInfoInDB("redis-commander", "", "password", true, req.Value); err != nil {
|
||||
if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
|
||||
} else {
|
||||
daemonMap["iptables"] = false
|
||||
}
|
||||
case "Dirver":
|
||||
case "Driver":
|
||||
if opts, ok := daemonMap["exec-opts"]; ok {
|
||||
if optsValue, isArray := opts.([]interface{}); isArray {
|
||||
for i := 0; i < len(optsValue); i++ {
|
||||
|
||||
@@ -37,7 +37,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
|
||||
if err != nil {
|
||||
return nginxFull, err
|
||||
}
|
||||
config := parser.NewStringParser(string(content)).Parse()
|
||||
config, err := parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return dto.NginxFull{}, err
|
||||
}
|
||||
config.FilePath = nginxConfig.FilePath
|
||||
nginxConfig.OldContent = string(content)
|
||||
nginxConfig.Config = config
|
||||
@@ -54,7 +57,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
|
||||
if err != nil {
|
||||
return nginxFull, err
|
||||
}
|
||||
siteConfig := parser.NewStringParser(string(siteNginxContent)).Parse()
|
||||
siteConfig, err := parser.NewStringParser(string(siteNginxContent)).Parse()
|
||||
if err != nil {
|
||||
return dto.NginxFull{}, err
|
||||
}
|
||||
siteConfig.FilePath = siteConfigPath
|
||||
siteNginxConfig.Config = siteConfig
|
||||
siteNginxConfig.OldContent = string(siteNginxContent)
|
||||
@@ -156,15 +162,22 @@ func deleteNginxConfig(scope string, params []dto.NginxParam, website *model.Web
|
||||
}
|
||||
|
||||
func getNginxParamsFromStaticFile(scope dto.NginxKey, newParams []dto.NginxParam) []dto.NginxParam {
|
||||
newConfig := &components.Config{}
|
||||
var (
|
||||
newConfig = &components.Config{}
|
||||
err error
|
||||
)
|
||||
|
||||
updateScope := "in"
|
||||
switch scope {
|
||||
case dto.SSL:
|
||||
newConfig = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
|
||||
newConfig, err = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
|
||||
case dto.CACHE:
|
||||
newConfig = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
|
||||
newConfig, err = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
|
||||
case dto.ProxyCache:
|
||||
newConfig = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
|
||||
newConfig, err = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
|
||||
}
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, dir := range newConfig.GetDirectives() {
|
||||
addParam := dto.NginxParam{
|
||||
|
||||
@@ -199,7 +199,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
_ = os.Remove(path.Join(secretDir, "server.crt"))
|
||||
_ = os.Remove(path.Join(secretDir, "server.key"))
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, false)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
|
||||
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
@@ -280,7 +280,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
if err := fileOp.WriteFile(path.Join(secretDir, "server.key.tmp"), strings.NewReader(key), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkCertValid(req.Domain); err != nil {
|
||||
if err := checkCertValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil {
|
||||
@@ -294,7 +294,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
}
|
||||
|
||||
sID, _ := c.Cookie(constant.SessionName)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, false)
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
@@ -418,7 +418,7 @@ func loadInfoFromCert() (*dto.SSLInfo, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkCertValid(domain string) error {
|
||||
func checkCertValid() error {
|
||||
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+69
-638
@@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -18,10 +16,10 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"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 {
|
||||
@@ -74,7 +72,7 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||
for _, snap := range req.Names {
|
||||
shortName := strings.TrimPrefix(snap, "snapshot_")
|
||||
nameItems := strings.Split(shortName, "_")
|
||||
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) != 3 {
|
||||
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) < 3 {
|
||||
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
|
||||
}
|
||||
if strings.HasSuffix(snap, ".tar.gz") {
|
||||
@@ -137,26 +135,13 @@ 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")
|
||||
}
|
||||
isNewSnapshot := isNewSnapVersion(snap.Version)
|
||||
isReTry := false
|
||||
if len(snap.InterruptStep) != 0 && !req.IsNew {
|
||||
isReTry = true
|
||||
}
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
|
||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(baseDir, os.ModePerm)
|
||||
@@ -164,151 +149,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusWaiting})
|
||||
_ = settingRepo.Update("SystemStatus", "Recovering")
|
||||
go func() {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
operation := "recover"
|
||||
if isReTry {
|
||||
operation = "re-recover"
|
||||
}
|
||||
if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) {
|
||||
itemBackupPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemBackupPath = strings.TrimSuffix(itemBackupPath, "/")
|
||||
ok, err := client.Download(fmt.Sprintf("%s/system_snapshot/%s.tar.gz", itemBackupPath, snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
|
||||
if err != nil || !ok {
|
||||
if req.ReDownload {
|
||||
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
|
||||
return
|
||||
}
|
||||
updateRecoverStatus(snap.ID, "Download", constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
if !isReTry || snap.InterruptStep == "Decompress" || (isReTry && req.ReDownload) {
|
||||
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
|
||||
if req.ReDownload {
|
||||
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
updateRecoverStatus(snap.ID, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
rootDir := fmt.Sprintf("%s/%s", baseDir, snap.Name)
|
||||
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "Readjson" {
|
||||
isReTry = false
|
||||
}
|
||||
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.BaseDir, snap.Name)
|
||||
_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
|
||||
|
||||
snapJson.OldBaseDir = global.CONF.System.BaseDir
|
||||
snapJson.OldPanelDataDir = path.Join(global.CONF.System.BaseDir, "1panel")
|
||||
snapJson.OldBackupDataDir = localDir
|
||||
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
|
||||
liveRestore := false
|
||||
|
||||
if !isNewSnapshot {
|
||||
if !isReTry || snap.InterruptStep == "LoadDockerJson" {
|
||||
snapJson.OldDockerDataDir, liveRestore, err = u.loadDockerDataDir()
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, "LoadDockerJson", constant.StatusFailed, fmt.Sprintf("load docker data dir failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if liveRestore {
|
||||
if err := u.updateLiveRestore(false); err != nil {
|
||||
updateRecoverStatus(snap.ID, "UpdateLiveRestore", constant.StatusFailed, fmt.Sprintf("update docker daemon.json live-restore conf failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
_ = u.saveJson(snapJson, rootDir)
|
||||
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if !isReTry || snap.InterruptStep == "DockerDir" {
|
||||
if err := u.handleDockerDatas(fileOp, operation, rootDir, snapJson.DockerDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
} else {
|
||||
if !isReTry || snap.InterruptStep == "DockerDir" {
|
||||
if err := u.handleDockerDatasWithSave(fileOp, operation, rootDir, ""); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
}
|
||||
|
||||
if !isReTry || snap.InterruptStep == "DaemonJson" {
|
||||
if err := u.handleDaemonJson(fileOp, operation, rootDir+"/docker/daemon.json", u.OriginalPath); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DaemonJson", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
|
||||
if !isReTry || snap.InterruptStep == "1PanelBinary" {
|
||||
if err := u.handlePanelBinary(fileOp, operation, recoverPanelDir+"/1panel", u.OriginalPath+"/1panel"); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if !isReTry || snap.InterruptStep == "1PctlBinary" {
|
||||
if err := u.handlePanelctlBinary(fileOp, operation, recoverPanelDir+"/1pctl", u.OriginalPath+"/1pctl"); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PctlBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if !isReTry || snap.InterruptStep == "1PanelService" {
|
||||
if err := u.handlePanelService(fileOp, operation, recoverPanelDir+"/1panel.service", u.OriginalPath+"/1panel.service"); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelService", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
|
||||
if !isReTry || snap.InterruptStep == "1PanelBackups" {
|
||||
if err := u.handleBackupDatas(fileOp, operation, rootDir, snapJson.BackupDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelBackups", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
|
||||
if !isReTry || snap.InterruptStep == "1PanelData" {
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, operation, rootDir, snapJson.PanelDataDir, localDir, snapJson.OldDockerDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelData", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
|
||||
if isNewSnapshot {
|
||||
_ = rebuildAllAppInstall()
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(rootDir)
|
||||
global.LOG.Info("recover successful")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
}()
|
||||
go u.HandleSnapshotRecover(snap, true, req)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -318,132 +159,9 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "Download" || snap.InterruptStep == "Decompress" || snap.InterruptStep == "Readjson" {
|
||||
return nil
|
||||
}
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
isNewSnapshot := isNewSnapVersion(snap.Version)
|
||||
|
||||
rootDir := path.Join(localDir, fmt.Sprintf("system/%s/%s", snap.Name, snap.Name))
|
||||
|
||||
_ = settingRepo.Update("SystemStatus", "Rollbacking")
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
|
||||
go func() {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
|
||||
if err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.OldBaseDir, snap.Name)
|
||||
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !isNewSnapshot {
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
}()
|
||||
if snap.InterruptStep == "DockerDir" {
|
||||
return
|
||||
}
|
||||
if snapJson.LiveRestoreEnabled {
|
||||
if err := u.updateLiveRestore(true); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if snap.InterruptStep == "UpdateLiveRestore" {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := u.handleDockerDatasWithSave(fileOp, "rollback", u.OriginalPath, ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = rebuildAllAppInstall()
|
||||
}()
|
||||
if snap.InterruptStep == "DockerDir" {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := u.handleDaemonJson(fileOp, "rollback", u.OriginalPath+"/daemon.json", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "DaemonJson" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelBinary(fileOp, "rollback", u.OriginalPath+"/1panel", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBinary" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelctlBinary(fileOp, "rollback", u.OriginalPath+"/1pctl", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PctlBinary" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelService(fileOp, "rollback", u.OriginalPath+"/1panel.service", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelService" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handleBackupDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldBackupDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBackups" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "rollback", u.OriginalPath, snapJson.OldPanelDataDir, "", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelData" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(rootDir)
|
||||
global.LOG.Info("rollback successful")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) saveJson(snapJson SnapshotJson, path string) error {
|
||||
remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
|
||||
if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", path), remarkInfo, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
req.IsNew = false
|
||||
snap.InterruptStep = "Readjson"
|
||||
go u.HandleSnapshotRecover(snap, false, req)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -475,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)
|
||||
|
||||
@@ -497,6 +216,7 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusWaiting})
|
||||
snapStatus, _ = snapshotRepo.GetStatus(snap.ID)
|
||||
if snapStatus.ID == 0 {
|
||||
snapStatus.SnapID = snap.ID
|
||||
@@ -546,17 +266,23 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
}
|
||||
snapPanelData(itemHelper, localDir, backupPanelDir)
|
||||
if snapStatus.PanelData != constant.StatusDone {
|
||||
snapPanelData(itemHelper, localDir, backupPanelDir)
|
||||
}
|
||||
if snapStatus.PanelData != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
}
|
||||
snapCompress(itemHelper, rootDir)
|
||||
if snapStatus.Compress != constant.StatusDone {
|
||||
snapCompress(itemHelper, rootDir)
|
||||
}
|
||||
if snapStatus.Compress != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
}
|
||||
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
|
||||
if snapStatus.Upload != constant.StatusDone {
|
||||
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
|
||||
}
|
||||
if snapStatus.Upload != constant.StatusDone {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
|
||||
return
|
||||
@@ -597,242 +323,6 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
|
||||
return snap.Name, nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation string, source, target string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
if err := handleSnapTar(source, target, "docker_data.tar.gz", ""); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
case "recover":
|
||||
if err := handleSnapTar(target, u.OriginalPath, "docker_data.tar.gz", ""); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("recover docker data failed, err: %v", err)
|
||||
}
|
||||
case "re-recover":
|
||||
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("re-recover docker data failed, err: %v", err)
|
||||
}
|
||||
case "rollback":
|
||||
if err := u.handleUnTar(source+"/docker_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("rollback docker data failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle docker data dir successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDockerDatasWithSave(fileOp files.FileOp, operation, source, target string) error {
|
||||
switch operation {
|
||||
case "recover":
|
||||
if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", u.OriginalPath); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "re-recover":
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "rollback":
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle docker data successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string, source, target string) error {
|
||||
daemonJsonPath := "/etc/docker/daemon.json"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
_, err := os.Stat(daemonJsonPath)
|
||||
if os.IsNotExist(err) {
|
||||
global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
|
||||
}
|
||||
if err == nil {
|
||||
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
|
||||
return fmt.Errorf("backup docker daemon.json failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
_, sourceErr := os.Stat(source)
|
||||
if os.IsNotExist(sourceErr) {
|
||||
_ = os.Remove(daemonJsonPath)
|
||||
}
|
||||
if sourceErr == nil {
|
||||
if err := fileOp.CopyFile(source, "/etc/docker"); err != nil {
|
||||
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle docker daemon.json successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation string, source, target string) error {
|
||||
panelPath := "/usr/local/bin/1panel"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
if _, err := os.Stat(panelPath); err != nil {
|
||||
return fmt.Errorf("1panel binary is not found in %s, err: %v", panelPath, err)
|
||||
} else {
|
||||
if err := cpBinary([]string{panelPath}, target); err != nil {
|
||||
return fmt.Errorf("backup 1panel binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
if _, err := os.Stat(source); err != nil {
|
||||
return fmt.Errorf("1panel binary is not found in snapshot, err: %v", err)
|
||||
} else {
|
||||
if err := cpBinary([]string{source}, "/usr/local/bin/1panel"); err != nil {
|
||||
return fmt.Errorf("recover 1panel binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle binary panel successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelctlBinary(fileOp files.FileOp, operation string, source, target string) error {
|
||||
panelctlPath := "/usr/local/bin/1pctl"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
if _, err := os.Stat(panelctlPath); err != nil {
|
||||
return fmt.Errorf("1pctl binary is not found in %s, err: %v", panelctlPath, err)
|
||||
} else {
|
||||
if err := cpBinary([]string{panelctlPath}, target); err != nil {
|
||||
return fmt.Errorf("backup 1pctl binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
if _, err := os.Stat(source); err != nil {
|
||||
return fmt.Errorf("1pctl binary is not found in snapshot, err: %v", err)
|
||||
} else {
|
||||
if err := cpBinary([]string{source}, "/usr/local/bin/1pctl"); err != nil {
|
||||
return fmt.Errorf("recover 1pctl binary failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle binary 1pactl successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation string, source, target string) error {
|
||||
panelServicePath := "/etc/systemd/system/1panel.service"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
if _, err := os.Stat(panelServicePath); err != nil {
|
||||
return fmt.Errorf("1panel service is not found in %s, err: %v", panelServicePath, err)
|
||||
} else {
|
||||
if err := cpBinary([]string{panelServicePath}, target); err != nil {
|
||||
return fmt.Errorf("backup 1panel service failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
|
||||
if _, err := os.Stat(source); err != nil {
|
||||
return fmt.Errorf("1panel service is not found in snapshot, err: %v", err)
|
||||
} else {
|
||||
if err := cpBinary([]string{source}, "/etc/systemd/system/1panel.service"); err != nil {
|
||||
return fmt.Errorf("recover 1panel service failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle panel service successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation string, source, target string) error {
|
||||
switch operation {
|
||||
case "recover":
|
||||
if err := handleSnapTar(target, u.OriginalPath, "1panel_backup.tar.gz", "./system;"); err != nil {
|
||||
return fmt.Errorf("restore original local backup dir data failed, err: %v", err)
|
||||
}
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("recover local backup dir data failed, err: %v", err)
|
||||
}
|
||||
case "re-recover":
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("retry recover local backup dir data failed, err: %v", err)
|
||||
}
|
||||
case "rollback":
|
||||
if err := u.handleUnTar(source+"/1panel_backup.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("rollback local backup dir data failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle backup data successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
exclusionRules := "./tmp;./log;./cache;"
|
||||
if strings.Contains(backupDir, source) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
|
||||
}
|
||||
if strings.Contains(dockerDir, source) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(dockerDir, source, "") + ";")
|
||||
}
|
||||
|
||||
if err := handleSnapTar(source, target, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
return fmt.Errorf("backup panel data failed, err: %v", err)
|
||||
}
|
||||
case "recover":
|
||||
exclusionRules := "./tmp;./log;./cache;./db/1Panel.db-*;"
|
||||
if strings.Contains(backupDir, target) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(backupDir, target, "") + ";")
|
||||
}
|
||||
if strings.Contains(dockerDir, target) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(dockerDir, target, "") + ";")
|
||||
}
|
||||
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": ""})
|
||||
checkPointOfWal()
|
||||
if err := handleSnapTar(target, u.OriginalPath, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
return fmt.Errorf("restore original panel data failed, err: %v", err)
|
||||
}
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": constant.StatusWaiting})
|
||||
|
||||
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("recover panel data failed, err: %v", err)
|
||||
}
|
||||
case "re-recover":
|
||||
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("retry recover panel data failed, err: %v", err)
|
||||
}
|
||||
case "rollback":
|
||||
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(source+"/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("rollback panel data failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) loadDockerDataDir() (string, bool, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("new docker client failed, err: %v", err)
|
||||
}
|
||||
info, err := client.Info(context.Background())
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("load docker info failed, err: %v", err)
|
||||
}
|
||||
return info.DockerRootDir, info.LiveRestoreEnabled, nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
|
||||
backups, _ := snapshotRepo.GetList(commonRepo.WithIdsIn(req.Ids))
|
||||
localDir, err := loadLocalDir()
|
||||
@@ -855,21 +345,22 @@ func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateRecoverStatus(id uint, interruptStep, status string, message string) {
|
||||
if status != constant.StatusSuccess {
|
||||
global.LOG.Errorf("recover failed, err: %s", message)
|
||||
func updateRecoverStatus(id uint, isRecover bool, interruptStep, status, message string) {
|
||||
if isRecover {
|
||||
if status != constant.StatusSuccess {
|
||||
global.LOG.Errorf("recover failed, err: %s", message)
|
||||
}
|
||||
if err := snapshotRepo.Update(id, map[string]interface{}{
|
||||
"interrupt_step": interruptStep,
|
||||
"recover_status": status,
|
||||
"recover_message": message,
|
||||
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("update snap recover status failed, err: %v", err)
|
||||
}
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
if err := snapshotRepo.Update(id, map[string]interface{}{
|
||||
"interrupt_step": interruptStep,
|
||||
"recover_status": status,
|
||||
"recover_message": message,
|
||||
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("update snap recover status failed, err: %v", err)
|
||||
}
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
}
|
||||
func updateRollbackStatus(id uint, status string, message string) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
if status == constant.StatusSuccess {
|
||||
if err := snapshotRepo.Update(id, map[string]interface{}{
|
||||
@@ -903,53 +394,6 @@ func cpBinary(src []string, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) updateLiveRestore(enabled bool) error {
|
||||
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
|
||||
return fmt.Errorf("load docker daemon.json conf failed, err: %v", err)
|
||||
}
|
||||
file, err := os.ReadFile(constant.DaemonJsonPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daemonMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal(file, &daemonMap)
|
||||
|
||||
if !enabled {
|
||||
delete(daemonMap, "live-restore")
|
||||
} else {
|
||||
daemonMap["live-restore"] = enabled
|
||||
}
|
||||
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("the docker service cannot be restarted")
|
||||
default:
|
||||
stdout, err := cmd.Exec("systemctl is-active docker")
|
||||
if string(stdout) == "active\n" && err == nil {
|
||||
global.LOG.Info("docker restart with new live-restore successful!")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
|
||||
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
||||
@@ -1002,64 +446,32 @@ func rebuildAllAppInstall() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isNewSnapVersion(version string) bool {
|
||||
versionItem := "v1.5.0"
|
||||
if version == versionItem {
|
||||
return true
|
||||
}
|
||||
version1s := strings.Split(version, ".")
|
||||
version2s := strings.Split(versionItem, ".")
|
||||
|
||||
n := min(len(version1s), len(version2s))
|
||||
re := regexp.MustCompile("[0-9]+")
|
||||
for i := 0; i < n; i++ {
|
||||
sVersion1s := re.FindAllString(version1s[i], -1)
|
||||
sVersion2s := re.FindAllString(version2s[i], -1)
|
||||
if len(sVersion1s) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(sVersion2s) == 0 {
|
||||
return false
|
||||
}
|
||||
v1num, _ := strconv.Atoi(sVersion1s[0])
|
||||
v2num, _ := strconv.Atoi(sVersion2s[0])
|
||||
if v1num == v2num {
|
||||
continue
|
||||
} else {
|
||||
return v1num > v2num
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func checkIsAllDone(snapID uint) bool {
|
||||
status, err := snapshotRepo.GetStatus(snapID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
isOK, _ := checkAllDone(status)
|
||||
return isOK
|
||||
}
|
||||
|
||||
func checkAllDone(status model.SnapshotStatus) (bool, string) {
|
||||
if status.Panel != constant.StatusDone {
|
||||
return false
|
||||
return false, status.Panel
|
||||
}
|
||||
if status.PanelInfo != constant.StatusDone {
|
||||
return false
|
||||
return false, status.PanelInfo
|
||||
}
|
||||
if status.DaemonJson != constant.StatusDone {
|
||||
return false
|
||||
return false, status.DaemonJson
|
||||
}
|
||||
if status.AppData != constant.StatusDone {
|
||||
return false
|
||||
return false, status.AppData
|
||||
}
|
||||
if status.BackupData != constant.StatusDone {
|
||||
return false
|
||||
return false, status.BackupData
|
||||
}
|
||||
return true
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func loadLogByStatus(status model.SnapshotStatus, logPath string) {
|
||||
@@ -1076,9 +488,28 @@ func loadLogByStatus(status model.SnapshotStatus, logPath string) {
|
||||
|
||||
file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("write snapshot logs failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,21 +97,23 @@ func snapAppData(snap snapHelper, targetDir string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rumtime := range runtimes {
|
||||
for _, runtime := range runtimes {
|
||||
for _, existImage := range existImages {
|
||||
if rumtime.Image == existImage && !duplicateMap[rumtime.Image] {
|
||||
imageSaveList = append(imageSaveList, rumtime.Image)
|
||||
duplicateMap[rumtime.Image] = true
|
||||
if runtime.Image == existImage && !duplicateMap[runtime.Image] {
|
||||
imageSaveList = append(imageSaveList, runtime.Image)
|
||||
duplicateMap[runtime.Image] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.LOG.Debugf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
if err != nil {
|
||||
snap.Status.AppData = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": std})
|
||||
return
|
||||
if len(imageSaveList) != 0 {
|
||||
global.LOG.Debugf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
||||
if err != nil {
|
||||
snap.Status.AppData = err.Error()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": std})
|
||||
return
|
||||
}
|
||||
}
|
||||
snap.Status.AppData = constant.StatusDone
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": constant.StatusDone})
|
||||
@@ -121,7 +123,7 @@ func snapBackup(snap snapHelper, localDir, targetDir string) {
|
||||
defer snap.Wg.Done()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"backup_data": constant.Running})
|
||||
status := constant.StatusDone
|
||||
if err := handleSnapTar(localDir, targetDir, "1panel_backup.tar.gz", "./system;"); err != nil {
|
||||
if err := handleSnapTar(localDir, targetDir, "1panel_backup.tar.gz", "./system;./system_snapshot;"); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
snap.Status.BackupData = status
|
||||
@@ -138,6 +140,8 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
||||
}
|
||||
|
||||
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
|
||||
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
|
||||
_ = settingRepo.Update("SystemIP", "")
|
||||
checkPointOfWal()
|
||||
if err := handleSnapTar(dataDir, targetDir, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
status = err.Error()
|
||||
@@ -146,6 +150,7 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
||||
|
||||
snap.Status.PanelData = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": status})
|
||||
_ = settingRepo.Update("SystemIP", sysIP.Value)
|
||||
}
|
||||
|
||||
func snapCompress(snap snapHelper, rootDir string) {
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"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/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover bool, req dto.SnapshotRecover) {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
|
||||
snapFileDir := ""
|
||||
if isRecover {
|
||||
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
|
||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(baseDir, os.ModePerm)
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "Backup" {
|
||||
if err := backupBeforeRecover(snap); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("handle backup before recover successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "Download" || req.ReDownload {
|
||||
if err := handleDownloadSnapshot(snap, baseDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debugf("download snapshot file to %s successful!", baseDir)
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "Decompress" {
|
||||
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("decompress snapshot file successful!", baseDir)
|
||||
req.IsNew = true
|
||||
}
|
||||
snapFileDir = fmt.Sprintf("%s/%s", baseDir, snap.Name)
|
||||
} else {
|
||||
snapFileDir = fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
if _, err := os.Stat(snapFileDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "", constant.StatusFailed, fmt.Sprintf("cannot find the backup file %s, please try to recover again.", snapFileDir))
|
||||
return
|
||||
}
|
||||
}
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", snapFileDir))
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "Readjson" {
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "AppData" {
|
||||
if err := recoverAppData(snapFileDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "DockerDir", constant.StatusFailed, fmt.Sprintf("handle recover app data failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover app data from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "DaemonJson" {
|
||||
fileOp := files.NewFileOp()
|
||||
if err := recoverDaemonJson(snapFileDir, fileOp); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "DaemonJson", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover daemon.json from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin/1panel"); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel binary from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin/1pctl"); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1pctl from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PanelService" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system/1panel.service"); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel service from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelBackups" {
|
||||
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_backup.tar.gz"), snapJson.BackupDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelBackups", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel backups from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelData" {
|
||||
checkPointOfWal()
|
||||
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel")); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelData", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1panel data from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
_ = rebuildAllAppInstall()
|
||||
restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"))
|
||||
|
||||
global.LOG.Info("recover successful")
|
||||
if !isRecover {
|
||||
oriPath := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
global.LOG.Debugf("remove the file %s after the operation is successful", oriPath)
|
||||
_ = os.RemoveAll(oriPath)
|
||||
} else {
|
||||
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
|
||||
_ = os.RemoveAll(path.Dir(snapFileDir))
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
}
|
||||
|
||||
func backupBeforeRecover(snap model.Snapshot) error {
|
||||
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
var wg sync.WaitGroup
|
||||
var status model.SnapshotStatus
|
||||
itemHelper := snapHelper{SnapID: 0, Status: &status, Wg: &wg, FileOp: files.NewFileOp(), Ctx: context.Background()}
|
||||
|
||||
jsonItem := SnapshotJson{
|
||||
BaseDir: global.CONF.System.BaseDir,
|
||||
BackupDataDir: global.CONF.System.Backup,
|
||||
PanelDataDir: path.Join(global.CONF.System.BaseDir, "1panel"),
|
||||
}
|
||||
_ = os.MkdirAll(path.Join(baseDir, "1panel"), os.ModePerm)
|
||||
_ = os.MkdirAll(path.Join(baseDir, "docker"), os.ModePerm)
|
||||
|
||||
wg.Add(5)
|
||||
itemHelper.Wg = &wg
|
||||
go snapJson(itemHelper, jsonItem, baseDir)
|
||||
go snapPanel(itemHelper, path.Join(baseDir, "1panel"))
|
||||
go snapDaemonJson(itemHelper, path.Join(baseDir, "docker"))
|
||||
go snapAppData(itemHelper, path.Join(baseDir, "docker"))
|
||||
go snapBackup(itemHelper, global.CONF.System.Backup, path.Join(baseDir, "1panel"))
|
||||
wg.Wait()
|
||||
|
||||
allDone, msg := checkAllDone(status)
|
||||
if !allDone {
|
||||
return errors.New(msg)
|
||||
}
|
||||
snapPanelData(itemHelper, global.CONF.System.BaseDir, path.Join(baseDir, "1panel"))
|
||||
if status.PanelData != constant.StatusDone {
|
||||
return errors.New(status.PanelData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleDownloadSnapshot(snap model.Snapshot, targetDir string) error {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pathItem := backup.BackupPath
|
||||
if backup.BackupPath != "/" {
|
||||
pathItem = strings.TrimPrefix(backup.BackupPath, "/")
|
||||
}
|
||||
filePath := fmt.Sprintf("%s/%s.tar.gz", targetDir, snap.Name)
|
||||
_ = os.RemoveAll(filePath)
|
||||
ok, err := client.Download(path.Join(pathItem, fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name)), filePath)
|
||||
if err != nil || !ok {
|
||||
return fmt.Errorf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverAppData(src string) error {
|
||||
if _, err := os.Stat(path.Join(src, "docker/docker_image.tar")); err != nil {
|
||||
global.LOG.Debug("no such docker images in snapshot")
|
||||
return nil
|
||||
}
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(src, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func recoverDaemonJson(src string, fileOp files.FileOp) error {
|
||||
daemonJsonPath := "/etc/docker/daemon.json"
|
||||
_, errSrc := os.Stat(src)
|
||||
_, errPath := os.Stat(daemonJsonPath)
|
||||
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
|
||||
global.LOG.Debug("the daemon.json file does not exist, nothing happens.")
|
||||
return nil
|
||||
}
|
||||
if errSrc == nil {
|
||||
if err := fileOp.CopyFile(path.Join(src, "docker/daemon.json"), "/etc/docker"); err != nil {
|
||||
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverPanel(src string, dst string) error {
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
return fmt.Errorf("file is not found in %s, err: %v", src, err)
|
||||
}
|
||||
global.LOG.Debugf(fmt.Sprintf("\\cp -f %s %s", src, dst))
|
||||
stdout, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", src, dst))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartCompose(composePath string) {
|
||||
composes, err := composeRepo.ListRecord()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, compose := range composes {
|
||||
pathItem := path.Join(composePath, compose.Name, "docker-compose.yml")
|
||||
if _, err := os.Stat(pathItem); err != nil {
|
||||
continue
|
||||
}
|
||||
upCmd := fmt.Sprintf("docker-compose -f %s up -d", pathItem)
|
||||
stdout, err := cmd.Exec(upCmd)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("%s failed, err: %v", upCmd, stdout)
|
||||
}
|
||||
}
|
||||
global.LOG.Debug("restart all compose successful!")
|
||||
}
|
||||
@@ -133,24 +133,24 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
|
||||
if err := cpBinary([]string{tmpDir + "/1panel"}, "/usr/local/bin/1panel"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 1)
|
||||
u.handleRollback(originalDir, 1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cpBinary([]string{tmpDir + "/1pctl"}, "/usr/local/bin/1pctl"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 2)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.System.BaseDir); err != nil {
|
||||
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 2)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cpBinary([]string{tmpDir + "/1panel.service"}, "/etc/systemd/system/1panel.service"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 3)
|
||||
u.handleRollback(originalDir, 3)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) {
|
||||
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
||||
dbPath := global.CONF.System.DbPath + "/1Panel.db"
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
if err := cpBinary([]string{originalDir + "/1Panel.db"}, dbPath); err != nil {
|
||||
@@ -261,8 +261,8 @@ func loadArch() (string, error) {
|
||||
if std == "armv7l\n" {
|
||||
return "armv7", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupport such arch: arm-%s", std)
|
||||
return "", fmt.Errorf("unsupported such arch: arm-%s", std)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupport such arch: %s", runtime.GOARCH)
|
||||
return "", fmt.Errorf("unsupported such arch: %s", runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,30 +820,27 @@ func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]r
|
||||
} else {
|
||||
checkIds = append(req.InstallIds, appInstall.ID)
|
||||
}
|
||||
for _, id := range checkIds {
|
||||
if err := syncByID(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(checkIds) > 0 {
|
||||
installList, _ := appInstallRepo.ListBy(commonRepo.WithIdsIn(checkIds))
|
||||
for _, install := range installList {
|
||||
if err = syncAppInstallStatus(&install); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, response.WebsitePreInstallCheck{
|
||||
Name: install.Name,
|
||||
Status: install.Status,
|
||||
Version: install.Version,
|
||||
AppName: install.App.Name,
|
||||
})
|
||||
if install.Status != constant.StatusRunning {
|
||||
if install.Status != constant.Running {
|
||||
showErr = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if showErr {
|
||||
return res, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) {
|
||||
@@ -1511,13 +1508,19 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
config = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
config, err = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "edit":
|
||||
par, err = parser.NewParser(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config = par.Parse()
|
||||
config, err = par.Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
oldContent, err = fileOp.GetContent(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -1533,6 +1536,7 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
_ = fileOp.Rename(backPath, includePath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
@@ -1610,7 +1614,10 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
return
|
||||
}
|
||||
proxyConfig.Content = string(content)
|
||||
config = parser.NewStringParser(string(content)).Parse()
|
||||
config, err = parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
directives := config.GetDirectives()
|
||||
|
||||
location, ok := directives[0].(*components.Location)
|
||||
@@ -2057,7 +2064,10 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config = oldPar.Parse()
|
||||
config, err = oldPar.Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
oldContent, err = fileOp.GetContent(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -2198,7 +2208,10 @@ func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig
|
||||
return
|
||||
}
|
||||
redirectConfig.Content = string(content)
|
||||
config = parser.NewStringParser(string(content)).Parse()
|
||||
config, err = parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dirs := config.GetDirectives()
|
||||
if len(dirs) > 0 {
|
||||
|
||||
@@ -2,13 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -18,6 +11,14 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -114,7 +115,7 @@ func createIndexFile(website *model.Website, runtime *model.Runtime) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createProxyFile(website *model.Website, runtime *model.Runtime) error {
|
||||
func createProxyFile(website *model.Website) error {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -132,7 +133,10 @@ func createProxyFile(website *model.Website, runtime *model.Runtime) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
config, err := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.FilePath = filePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
@@ -188,7 +192,7 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website,
|
||||
}
|
||||
}
|
||||
if website.Type == constant.Proxy {
|
||||
if err := createProxyFile(website, runtime); err != nil {
|
||||
if err := createProxyFile(website); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -208,7 +212,10 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
nginxFileName := website.Alias + ".conf"
|
||||
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName)
|
||||
nginxContent := string(nginx_conf.WebsiteDefault)
|
||||
config := parser.NewStringParser(nginxContent).Parse()
|
||||
config, err := parser.NewStringParser(nginxContent).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers := config.FindServers()
|
||||
if len(servers) == 0 {
|
||||
return errors.New("nginx config is not valid")
|
||||
@@ -872,7 +879,10 @@ func ChangeHSTSConfig(enable bool, nginxInstall model.AppInstall, website model.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config := par.Parse()
|
||||
config, err := par.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.FilePath = path
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
Rebuilding = "Rebuilding"
|
||||
Syncing = "Syncing"
|
||||
SyncSuccess = "SyncSuccess"
|
||||
SyncErr = "SyncErr"
|
||||
Paused = "Paused"
|
||||
|
||||
ContainerPrefix = "1Panel-"
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ var (
|
||||
ErrAppNameExist = "ErrAppNameExist"
|
||||
ErrFileNotFound = "ErrFileNotFound"
|
||||
ErrFileParseApp = "ErrFileParseApp"
|
||||
ErrAppParamKey = "ErrAppParamKey"
|
||||
)
|
||||
|
||||
// website
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/init/cache/badger_db"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/session/psession"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
@@ -23,4 +24,6 @@ var (
|
||||
Cron *cron.Cron
|
||||
MonitorCronID cron.EntryID
|
||||
OneDriveCronID cron.EntryID
|
||||
|
||||
I18n *i18n.Localizer
|
||||
)
|
||||
|
||||
+25
-27
@@ -2,9 +2,9 @@ package i18n
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"strings"
|
||||
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
)
|
||||
|
||||
func GetMsgWithMap(key string, maps map[string]interface{}) string {
|
||||
content := ""
|
||||
var content string
|
||||
if maps == nil {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
} else {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: maps,
|
||||
})
|
||||
@@ -32,13 +32,13 @@ func GetMsgWithMap(key string, maps map[string]interface{}) string {
|
||||
}
|
||||
|
||||
func GetErrMsg(key string, maps map[string]interface{}) string {
|
||||
content := ""
|
||||
var content string
|
||||
if maps == nil {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
} else {
|
||||
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: maps,
|
||||
})
|
||||
@@ -47,7 +47,7 @@ func GetErrMsg(key string, maps map[string]interface{}) string {
|
||||
}
|
||||
|
||||
func GetMsgByKey(key string) string {
|
||||
content := ginI18n.MustGetMessage(&i18n.LocalizeConfig{
|
||||
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
return content
|
||||
@@ -55,24 +55,22 @@ func GetMsgByKey(key string) string {
|
||||
|
||||
//go:embed lang/*
|
||||
var fs embed.FS
|
||||
var bundle *i18n.Bundle
|
||||
|
||||
func GinI18nLocalize() gin.HandlerFunc {
|
||||
return ginI18n.Localize(
|
||||
ginI18n.WithBundle(&ginI18n.BundleCfg{
|
||||
RootPath: "./lang",
|
||||
AcceptLanguage: []language.Tag{language.Chinese, language.English, language.TraditionalChinese},
|
||||
DefaultLanguage: language.Chinese,
|
||||
FormatBundleFile: "yaml",
|
||||
UnmarshalFunc: yaml.Unmarshal,
|
||||
Loader: &ginI18n.EmbedLoader{FS: fs},
|
||||
}),
|
||||
ginI18n.WithGetLngHandle(
|
||||
func(context *gin.Context, defaultLng string) string {
|
||||
lng := context.GetHeader("Accept-Language")
|
||||
if lng == "" {
|
||||
return defaultLng
|
||||
}
|
||||
return lng
|
||||
},
|
||||
))
|
||||
func UseI18n() gin.HandlerFunc {
|
||||
return func(context *gin.Context) {
|
||||
lang := context.GetHeader("Accept-Language")
|
||||
if lang == "" {
|
||||
lang = "zh"
|
||||
}
|
||||
global.I18n = i18n.NewLocalizer(bundle, lang)
|
||||
}
|
||||
}
|
||||
|
||||
func Init() {
|
||||
bundle = i18n.NewBundle(language.Chinese)
|
||||
bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/zh.yaml")
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/en.yaml")
|
||||
_, _ = bundle.LoadMessageFileFS(fs, "lang/zh-Hant.yaml")
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: 'The current 1Panel version is too low to update the app store
|
||||
ErrAppNameExist: 'App name is already exist'
|
||||
AppStoreIsSyncing: 'The App Store is syncing, please try again later'
|
||||
ErrGetCompose: "Failed to obtain docker-compose.yml file! {{ .detail }}"
|
||||
ErrAppWarn: "Abnormal status, please check the log"
|
||||
ErrAppParamKey: "Parameter {{ .name }} field exception"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "File can not read"
|
||||
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: '當前 1Panel 版本過低,無法更新應用商店,請
|
||||
ErrAppNameExist: '應用名稱已存在'
|
||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
|
||||
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
|
||||
ErrAppWarn: "狀態異常,請查看日誌"
|
||||
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
|
||||
@@ -59,6 +59,8 @@ ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请
|
||||
ErrAppNameExist: '应用名称已存在'
|
||||
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
|
||||
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
|
||||
ErrAppWarn: "状态异常,请查看日志"
|
||||
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
|
||||
@@ -23,7 +23,7 @@ func syncApp() {
|
||||
|
||||
func syncInstalledApp() {
|
||||
if err := service.NewIAppInstalledService().SyncAll(true); err != nil {
|
||||
global.LOG.Errorf("sync instaled app error: %s", err.Error())
|
||||
global.LOG.Errorf("sync installed app error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-13
@@ -5,7 +5,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
@@ -18,18 +17,6 @@ func NewCacheDB(db *badger.DB) *Cache {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) SetNX(key string, value interface{}) error {
|
||||
err := c.db.Update(func(txn *badger.Txn) error {
|
||||
_, err := txn.Get([]byte(key))
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
v := []byte(fmt.Sprintf("%v", value))
|
||||
return txn.Set([]byte(key), v)
|
||||
}
|
||||
return err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, value interface{}) error {
|
||||
err := c.db.Update(func(txn *badger.Txn) error {
|
||||
v := []byte(fmt.Sprintf("%v", value))
|
||||
|
||||
@@ -17,13 +17,13 @@ import (
|
||||
func Init() {
|
||||
if _, err := os.Stat(global.CONF.System.DbPath); err != nil {
|
||||
if err := os.MkdirAll(global.CONF.System.DbPath, os.ModePerm); err != nil {
|
||||
panic(fmt.Errorf("init db dir falied, err: %v", err))
|
||||
panic(fmt.Errorf("init db dir failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
fullPath := global.CONF.System.DbPath + "/" + global.CONF.System.DbFile
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
if _, err := os.Create(fullPath); err != nil {
|
||||
panic(fmt.Errorf("init db file falied, err: %v", err))
|
||||
panic(fmt.Errorf("init db file failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ type MineFormatter struct{}
|
||||
func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
detailInfo := ""
|
||||
if entry.Caller != nil {
|
||||
funcion := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/backend/", "")
|
||||
detailInfo = fmt.Sprintf("(%s: %d)", funcion, entry.Caller.Line)
|
||||
function := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/backend/", "")
|
||||
detailInfo = fmt.Sprintf("(%s: %d)", function, entry.Caller.Line)
|
||||
}
|
||||
if len(entry.Data) == 0 {
|
||||
msg := fmt.Sprintf("[%s] [%s] %s %s \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo)
|
||||
|
||||
@@ -330,7 +330,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
Source: snap.From,
|
||||
BackupType: snap.From,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: job.CreatedAt,
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
@@ -348,7 +348,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
Source: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: job.CreatedAt,
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
_ = tx.Create(&item).Error
|
||||
@@ -364,7 +364,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
|
||||
FileName: path.Base(record.File),
|
||||
BackupType: mapAccount[uint(job.TargetDirID)].Type,
|
||||
BaseModel: model.BaseModel{
|
||||
CreatedAt: job.CreatedAt,
|
||||
CreatedAt: record.CreatedAt,
|
||||
},
|
||||
}
|
||||
if record.FromLocal {
|
||||
|
||||
@@ -9,11 +9,9 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/docs"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/web"
|
||||
"github.com/gin-contrib/gzip"
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -53,10 +51,7 @@ func Routers() *gin.Engine {
|
||||
c.Writer.Flush()
|
||||
})
|
||||
|
||||
Router.Use(i18n.GinI18nLocalize())
|
||||
Router.SetFuncMap(template.FuncMap{
|
||||
"Localize": ginI18n.GetMessage,
|
||||
})
|
||||
Router.Use(i18n.UseI18n())
|
||||
|
||||
swaggerRouter := Router.Group("1panel")
|
||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||
|
||||
@@ -16,6 +16,7 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
|
||||
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
|
||||
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
|
||||
func Start() {
|
||||
viper.Init()
|
||||
i18n.Init()
|
||||
log.Init()
|
||||
db.Init()
|
||||
migration.Init()
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
func loadParamFromVars(key string, vars map[string]interface{}) string {
|
||||
if _, ok := vars[key]; !ok {
|
||||
if key != "bucket" {
|
||||
if key != "bucket" && key != "port" {
|
||||
global.LOG.Errorf("load param %s from vars failed, err: not exist!", key)
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -116,7 +116,7 @@ func (o oneDriveClient) Upload(src, target string) (bool, error) {
|
||||
}
|
||||
var isOk bool
|
||||
if fileInfo.Size() < 4*1024*1024 {
|
||||
isOk, err = o.upSmall(ctx, src, folderID, fileInfo.Size())
|
||||
isOk, err = o.upSmall(src, folderID, fileInfo.Size())
|
||||
} else {
|
||||
isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size())
|
||||
}
|
||||
@@ -300,7 +300,7 @@ func (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOf
|
||||
return req, err
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
func (o *oneDriveClient) upSmall(srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
file, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
@@ -21,6 +22,9 @@ type sftpClient struct {
|
||||
func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
|
||||
address := loadParamFromVars("address", vars)
|
||||
port := loadParamFromVars("port", vars)
|
||||
if len(port) == 0 {
|
||||
global.LOG.Errorf("load param port from vars failed, err: not exist!")
|
||||
}
|
||||
password := loadParamFromVars("password", vars)
|
||||
username := loadParamFromVars("username", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
|
||||
+51
-39
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -14,20 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func Exec(cmdStr string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
}
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
return stdout.String(), nil
|
||||
return ExecWithTimeOut(cmdStr, 20*time.Second)
|
||||
}
|
||||
|
||||
func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
|
||||
@@ -46,19 +32,28 @@ func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
|
||||
}
|
||||
|
||||
func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
after := time.After(timeout)
|
||||
select {
|
||||
case <-after:
|
||||
_ = cmd.Process.Kill()
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
@@ -75,9 +70,6 @@ func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) er
|
||||
}
|
||||
|
||||
func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -88,13 +80,24 @@ func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Durati
|
||||
cmd.Dir = workdir
|
||||
cmd.Stdout = file
|
||||
cmd.Stderr = file
|
||||
|
||||
err = cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return buserr.New(constant.ErrCmdTimeout)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
after := time.After(timeout)
|
||||
select {
|
||||
case <-after:
|
||||
_ = cmd.Process.Kill()
|
||||
return buserr.New(constant.ErrCmdTimeout)
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Execf(cmdStr string, a ...interface{}) (string, error) {
|
||||
@@ -122,20 +125,29 @@ func ExecWithCheck(name string, a ...string) (string, error) {
|
||||
}
|
||||
|
||||
func ExecScript(scriptPath, workDir string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
cmd := exec.Command("bash", scriptPath)
|
||||
cmd.Dir = workDir
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Dir = workDir
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
after := time.After(10 * time.Minute)
|
||||
select {
|
||||
case <-after:
|
||||
_ = cmd.Process.Kill()
|
||||
return "", buserr.New(constant.ErrCmdTimeout)
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return handleErr(stdout, stderr, err)
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"golang.org/x/net/idna"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
@@ -15,6 +14,8 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -143,15 +144,6 @@ func ScanPortWithProto(port int, proto string) bool {
|
||||
return ScanPort(port)
|
||||
}
|
||||
|
||||
func ExistWithStrArray(str string, arr []string) bool {
|
||||
for _, a := range arr {
|
||||
if strings.Contains(a, str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsNum(s string) bool {
|
||||
_, err := strconv.ParseFloat(s, 64)
|
||||
return err == nil
|
||||
|
||||
@@ -2,14 +2,19 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/joho/godotenv"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/loader"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type ComposeService struct {
|
||||
@@ -17,8 +22,7 @@ type ComposeService struct {
|
||||
project *types.Project
|
||||
}
|
||||
|
||||
func (s *ComposeService) SetProject(project *types.Project) {
|
||||
s.project = project
|
||||
func UpComposeProject(project *types.Project) error {
|
||||
for i, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
api.ProjectLabel: project.Name,
|
||||
@@ -30,47 +34,35 @@ func (s *ComposeService) SetProject(project *types.Project) {
|
||||
}
|
||||
project.Services[i] = s
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeUp() error {
|
||||
return s.Up(context.Background(), s.project, api.UpOptions{
|
||||
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ops []command.CLIOption
|
||||
ops = append(ops, command.WithAPIClient(apiClient), command.WithDefaultContextStoreConfig())
|
||||
cli, err := command.NewDockerCli(ops...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cliOp := flags.NewClientOptions()
|
||||
if err = cli.Initialize(cliOp); err != nil {
|
||||
return err
|
||||
}
|
||||
service := compose.NewComposeService(cli)
|
||||
composeService := ComposeService{Service: service}
|
||||
|
||||
return composeService.Up(context.Background(), project, api.UpOptions{
|
||||
Create: api.CreateOptions{
|
||||
Timeout: getComposeTimeout(),
|
||||
},
|
||||
Start: api.StartOptions{
|
||||
WaitTimeout: *getComposeTimeout(),
|
||||
Wait: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeDown() error {
|
||||
return s.Down(context.Background(), s.project.Name, api.DownOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeStart() error {
|
||||
return s.Start(context.Background(), s.project.Name, api.StartOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeRestart() error {
|
||||
return s.Restart(context.Background(), s.project.Name, api.RestartOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeStop() error {
|
||||
return s.Stop(context.Background(), s.project.Name, api.StopOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeCreate() error {
|
||||
return s.Create(context.Background(), s.project, api.CreateOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeBuild() error {
|
||||
return s.Build(context.Background(), s.project, api.BuildOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposePull() error {
|
||||
return s.Pull(context.Background(), s.project, api.PullOptions{})
|
||||
}
|
||||
|
||||
func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) {
|
||||
var configFiles []types.ConfigFile
|
||||
configFiles = append(configFiles, types.ConfigFile{
|
||||
|
||||
@@ -3,6 +3,8 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
|
||||
@@ -44,24 +46,17 @@ func NewDockerClient() (*client.Client, error) {
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func (c Client) ListAllContainers() ([]types.Container, error) {
|
||||
var options types.ContainerListOptions
|
||||
containers, err := c.cli.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (c Client) ListContainersByName(names []string) ([]types.Container, error) {
|
||||
var (
|
||||
options types.ContainerListOptions
|
||||
res []types.Container
|
||||
options container.ListOptions
|
||||
namesMap = make(map[string]bool)
|
||||
res []types.Container
|
||||
)
|
||||
options.All = true
|
||||
if len(names) > 0 {
|
||||
var array []filters.KeyValuePair
|
||||
for _, n := range names {
|
||||
namesMap["/"+n] = true
|
||||
array = append(array, filters.Arg("name", n))
|
||||
}
|
||||
options.Filters = filters.NewArgs(array...)
|
||||
@@ -70,9 +65,9 @@ func (c Client) ListContainersByName(names []string) ([]types.Container, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, container := range containers {
|
||||
if container.Names[0] == "/"+names[0] {
|
||||
res = append(res, container)
|
||||
for _, con := range containers {
|
||||
if _, ok := namesMap[con.Names[0]]; ok {
|
||||
res = append(res, con)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
@@ -61,12 +59,6 @@ func StringDecrypt(text string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func Md5(str string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func padding(plaintext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(plaintext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
|
||||
@@ -7,11 +7,6 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
cZip "github.com/klauspost/compress/zip"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
@@ -23,6 +18,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
cZip "github.com/klauspost/compress/zip"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/pkg/errors"
|
||||
@@ -85,10 +86,6 @@ func (f FileOp) DeleteFile(dst string) error {
|
||||
return f.Fs.Remove(dst)
|
||||
}
|
||||
|
||||
func (f FileOp) Delete(dst string) error {
|
||||
return os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func (f FileOp) CleanDir(dst string) error {
|
||||
return cmd.ExecCmd(fmt.Sprintf("rm -rf %s/*", dst))
|
||||
}
|
||||
@@ -129,14 +126,6 @@ func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) Chmod(dst string, mode fs.FileMode) error {
|
||||
return f.Fs.Chmod(dst, mode)
|
||||
}
|
||||
|
||||
func (f FileOp) Chown(dst string, uid int, gid int) error {
|
||||
return f.Fs.Chown(dst, uid, gid)
|
||||
}
|
||||
|
||||
func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
|
||||
cmdStr := fmt.Sprintf(`chown %s:%s "%s"`, uid, gid, dst)
|
||||
if sub {
|
||||
@@ -563,40 +552,6 @@ func (f FileOp) Decompress(srcFile string, dst string, cType CompressType) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) Backup(srcFile string) (string, error) {
|
||||
backupPath := srcFile + "_bak"
|
||||
info, _ := f.Fs.Stat(backupPath)
|
||||
if info != nil {
|
||||
if info.IsDir() {
|
||||
_ = f.DeleteDir(backupPath)
|
||||
} else {
|
||||
_ = f.DeleteFile(backupPath)
|
||||
}
|
||||
}
|
||||
if err := f.Rename(srcFile, backupPath); err != nil {
|
||||
return backupPath, err
|
||||
}
|
||||
|
||||
return backupPath, nil
|
||||
}
|
||||
|
||||
func (f FileOp) CopyAndBackup(src string) (string, error) {
|
||||
backupPath := src + "_bak"
|
||||
info, _ := f.Fs.Stat(backupPath)
|
||||
if info != nil {
|
||||
if info.IsDir() {
|
||||
_ = f.DeleteDir(backupPath)
|
||||
} else {
|
||||
_ = f.DeleteFile(backupPath)
|
||||
}
|
||||
}
|
||||
_ = f.CreateDir(backupPath, 0755)
|
||||
if err := f.Copy(src, backupPath); err != nil {
|
||||
return backupPath, err
|
||||
}
|
||||
return backupPath, nil
|
||||
}
|
||||
|
||||
func ZipFile(files []archiver.File, dst afero.File) error {
|
||||
zw := zip.NewWriter(dst)
|
||||
defer zw.Close()
|
||||
|
||||
@@ -3,6 +3,7 @@ package files
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/spf13/afero"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -87,16 +88,20 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
reader := bufio.NewReaderSize(file, 8192)
|
||||
|
||||
var lines []string
|
||||
currentLine := 0
|
||||
startLine := (page - 1) * pageSize
|
||||
endLine := startLine + pageSize
|
||||
|
||||
for scanner.Scan() {
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if currentLine >= startLine && currentLine < endLine {
|
||||
lines = append(lines, scanner.Text())
|
||||
lines = append(lines, string(line))
|
||||
}
|
||||
currentLine++
|
||||
if currentLine >= endLine {
|
||||
@@ -104,10 +109,6 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
isEndOfFile := currentLine < endLine
|
||||
|
||||
return lines, isEndOfFile, nil
|
||||
|
||||
@@ -141,7 +141,7 @@ func (f *Ufw) Port(port FireInfo, operation string) error {
|
||||
case "drop":
|
||||
port.Strategy = "deny"
|
||||
default:
|
||||
return fmt.Errorf("unsupport strategy %s", port.Strategy)
|
||||
return fmt.Errorf("unsupported strategy %s", port.Strategy)
|
||||
}
|
||||
if cmd.CheckIllegal(port.Protocol, port.Port) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
@@ -168,7 +168,7 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error {
|
||||
case "drop":
|
||||
rule.Strategy = "deny"
|
||||
default:
|
||||
return fmt.Errorf("unsupport strategy %s", rule.Strategy)
|
||||
return fmt.Errorf("unsupported strategy %s", rule.Strategy)
|
||||
}
|
||||
|
||||
if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) {
|
||||
|
||||
@@ -17,20 +17,3 @@ func GetIniValue(filePath, Group, Key string) (string, error) {
|
||||
}
|
||||
return startKey.Value(), nil
|
||||
}
|
||||
|
||||
func SetIniValue(filePath, Group, Key, value string) error {
|
||||
cfg, err := ini.Load(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service, err := cfg.GetSection(Group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKey := service.Key(Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKey.SetValue(value)
|
||||
return cfg.SaveTo(filePath)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
|
||||
)
|
||||
|
||||
@@ -50,6 +51,7 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
global.LOG.Errorf("test mysql conn failed, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
@@ -69,6 +70,8 @@ type AccessChangeInfo struct {
|
||||
|
||||
type BackupInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
Format string `json:"format"`
|
||||
TargetDir string `json:"targetDir"`
|
||||
FileName string `json:"fileName"`
|
||||
@@ -78,6 +81,8 @@ type BackupInfo struct {
|
||||
|
||||
type RecoverInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
Format string `json:"format"`
|
||||
SourceFile string `json:"sourceFile"`
|
||||
|
||||
@@ -101,45 +106,30 @@ var formatMap = map[string]string{
|
||||
"big5": "big5_chinese_ci",
|
||||
}
|
||||
|
||||
func VerifyPeerCertFunc(pool *x509.CertPool) func([][]byte, [][]*x509.Certificate) error {
|
||||
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
if len(rawCerts) == 0 {
|
||||
return errors.New("no certificates available to verify")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := x509.VerifyOptions{Roots: pool}
|
||||
if _, err = cert.Verify(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ConnWithSSL(ssl, skipVerify bool, clientKey, clientCert, rootCert string) (string, error) {
|
||||
if !ssl {
|
||||
return "", nil
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: skipVerify,
|
||||
}
|
||||
if len(rootCert) != 0 {
|
||||
pool := x509.NewCertPool()
|
||||
if ok := pool.AppendCertsFromPEM([]byte(rootCert)); !ok {
|
||||
global.LOG.Error("append certs from pem failed")
|
||||
return "", errors.New("unable to append root cert to pool")
|
||||
}
|
||||
tlsConfig.RootCAs = pool
|
||||
}
|
||||
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
if len(clientCert) != 0 && len(clientKey) != 0 {
|
||||
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
if err := mysql.RegisterTLSConfig("cloudsql", &tls.Config{
|
||||
RootCAs: pool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: skipVerify,
|
||||
VerifyPeerCertificate: VerifyPeerCertFunc(pool),
|
||||
}); err != nil {
|
||||
if err := mysql.RegisterTLSConfig("cloudsql", tlsConfig); err != nil {
|
||||
global.LOG.Errorf("register tls config failed, err: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return "&tls=cloudsql", nil
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,7 +16,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/helper"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type Remote struct {
|
||||
@@ -229,56 +233,63 @@ func (r *Remote) Backup(info BackupInfo) error {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
||||
}
|
||||
}
|
||||
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
|
||||
|
||||
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
|
||||
outfile, _ := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, 0755)
|
||||
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
|
||||
image, err := loadImage(info.Type, info.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
|
||||
backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysqldump -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
|
||||
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
|
||||
|
||||
f, _ := os.OpenFile(fileNameItem, os.O_RDWR|os.O_CREATE, 0755)
|
||||
defer f.Close()
|
||||
if err := helper.Dump(dns, helper.WithData(), helper.WithDropTable(), helper.WithWriter(f)); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Debug(backupCmd)
|
||||
cmd := exec.Command("bash", "-c", backupCmd)
|
||||
|
||||
gzipCmd := exec.Command("gzip", fileNameItem)
|
||||
stdout, err := gzipCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("gzip file %s failed, stdout: %v, err: %v", strings.TrimSuffix(info.FileName, ".gz"), string(stdout), err)
|
||||
}
|
||||
gzipCmd := exec.Command("gzip", "-cf")
|
||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
||||
gzipCmd.Stdout = outfile
|
||||
_ = gzipCmd.Start()
|
||||
_ = cmd.Run()
|
||||
_ = gzipCmd.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) Recover(info RecoverInfo) error {
|
||||
fileName := info.SourceFile
|
||||
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
|
||||
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
|
||||
gzipCmd := exec.Command("gunzip", info.SourceFile)
|
||||
stdout, err := gzipCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err)
|
||||
}
|
||||
defer func() {
|
||||
gzipCmd := exec.Command("gzip", fileName)
|
||||
_, _ = gzipCmd.CombinedOutput()
|
||||
}()
|
||||
}
|
||||
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
|
||||
fi, _ := os.Open(info.SourceFile)
|
||||
defer fi.Close()
|
||||
|
||||
f, err := os.Open(fileName)
|
||||
image, err := loadImage(info.Type, info.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := helper.Source(dns, f, helper.WithMergeInsert(1000)); err != nil {
|
||||
return err
|
||||
|
||||
recoverCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysql -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
|
||||
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
|
||||
|
||||
global.LOG.Debug(recoverCmd)
|
||||
cmd := exec.Command("bash", "-c", recoverCmd)
|
||||
|
||||
if strings.HasSuffix(info.SourceFile, ".gz") {
|
||||
gzipFile, err := os.Open(info.SourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipFile.Close()
|
||||
gzipReader, err := gzip.NewReader(gzipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
cmd.Stdin = gzipReader
|
||||
} else {
|
||||
cmd.Stdin = fi
|
||||
}
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
|
||||
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
|
||||
return errors.New(stdStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -389,3 +400,45 @@ func (r *Remote) ExecSQLForHosts(timeout uint) ([]string, error) {
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func loadImage(dbType, version string) (string, error) {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
for _, tag := range image.RepoTags {
|
||||
if !strings.HasPrefix(tag, dbType+":") {
|
||||
continue
|
||||
}
|
||||
if dbType == "mariadb" && strings.HasPrefix(tag, "mariadb:") {
|
||||
return tag, nil
|
||||
}
|
||||
if strings.HasPrefix(version, "5.6") && strings.HasPrefix(tag, "mysql:5.6") {
|
||||
return tag, nil
|
||||
}
|
||||
if strings.HasPrefix(version, "5.7") && strings.HasPrefix(tag, "mysql:5.7") {
|
||||
return tag, nil
|
||||
}
|
||||
if strings.HasPrefix(version, "8.") && strings.HasPrefix(tag, "mysql:8.") {
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if dbType == "mariadb" || version == "8.x" {
|
||||
return "mysql:8.2.0", nil
|
||||
}
|
||||
return "mysql:" + version, nil
|
||||
}
|
||||
|
||||
func sslSkip(version string) string {
|
||||
if strings.HasPrefix(version, "5.6") || strings.HasPrefix(version, "5.7") {
|
||||
return "--skip-ssl"
|
||||
}
|
||||
return "--ssl-mode=DISABLED"
|
||||
}
|
||||
|
||||
@@ -38,18 +38,6 @@ func WithData() DumpOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithTables(tables ...string) DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.tables = tables
|
||||
}
|
||||
}
|
||||
|
||||
func WithAllTable() DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.isAllTable = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithWriter(writer io.Writer) DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.writer = writer
|
||||
@@ -152,7 +140,7 @@ func Dump(dns string, opts ...DumpOption) error {
|
||||
var columns []string
|
||||
columns, err = lineRows.Columns()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get columes falied, err: %v", err)
|
||||
global.LOG.Errorf("get columes failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
columnTypes, err := lineRows.ColumnTypes()
|
||||
|
||||
@@ -19,24 +19,12 @@ type sourceOption struct {
|
||||
}
|
||||
type SourceOption func(*sourceOption)
|
||||
|
||||
func WithDryRun() SourceOption {
|
||||
return func(o *sourceOption) {
|
||||
o.dryRun = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithMergeInsert(size int) SourceOption {
|
||||
return func(o *sourceOption) {
|
||||
o.mergeInsert = size
|
||||
}
|
||||
}
|
||||
|
||||
func WithDebug() SourceOption {
|
||||
return func(o *sourceOption) {
|
||||
o.debug = true
|
||||
}
|
||||
}
|
||||
|
||||
type dbWrapper struct {
|
||||
DB *sql.DB
|
||||
debug bool
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package components
|
||||
|
||||
type Block struct {
|
||||
Line int
|
||||
Comment string
|
||||
Directives []IDirective
|
||||
Line int
|
||||
Comment string
|
||||
Directives []IDirective
|
||||
IsLuaBlock bool
|
||||
LiteralCode string
|
||||
}
|
||||
|
||||
func (b *Block) GetDirectives() []IDirective {
|
||||
@@ -18,6 +20,10 @@ func (b *Block) GetLine() int {
|
||||
return b.Line
|
||||
}
|
||||
|
||||
func (b *Block) GetCodeBlock() string {
|
||||
return b.LiteralCode
|
||||
}
|
||||
|
||||
func (b *Block) FindDirectives(directiveName string) []IDirective {
|
||||
directives := make([]IDirective, 0)
|
||||
for _, directive := range b.GetDirectives() {
|
||||
|
||||
@@ -5,19 +5,6 @@ type Config struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
func (c *Config) FindDirectives(directiveName string) []IDirective {
|
||||
return c.Block.FindDirectives(directiveName)
|
||||
}
|
||||
|
||||
func (c *Config) FindUpstreams() []*Upstream {
|
||||
var upstreams []*Upstream
|
||||
directives := c.Block.FindDirectives("upstream")
|
||||
for _, directive := range directives {
|
||||
upstreams = append(upstreams, directive.(*Upstream))
|
||||
}
|
||||
return upstreams
|
||||
}
|
||||
|
||||
func (c *Config) FindServers() []*Server {
|
||||
var servers []*Server
|
||||
directives := c.Block.FindDirectives("server")
|
||||
|
||||
@@ -11,6 +11,10 @@ type Http struct {
|
||||
Line int
|
||||
}
|
||||
|
||||
func (h *Http) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *Http) GetComment() string {
|
||||
return h.Comment
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ type Location struct {
|
||||
Replaces map[string]string
|
||||
}
|
||||
|
||||
func (l *Location) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewLocation(directive IDirective) *Location {
|
||||
location := &Location{
|
||||
Modifier: "",
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type LuaBlock struct {
|
||||
Directives []IDirective
|
||||
Name string
|
||||
Comment string
|
||||
LuaCode string
|
||||
Line int
|
||||
}
|
||||
|
||||
func NewLuaBlock(directive IDirective) (*LuaBlock, error) {
|
||||
if block := directive.GetBlock(); block != nil {
|
||||
lb := &LuaBlock{
|
||||
Directives: []IDirective{},
|
||||
Name: directive.GetName(),
|
||||
LuaCode: block.GetCodeBlock(),
|
||||
}
|
||||
|
||||
lb.Directives = append(lb.Directives, block.GetDirectives()...)
|
||||
return lb, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%s must have a block", directive.GetName())
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetName() string {
|
||||
return lb.Name
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetParameters() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetDirectives() []IDirective {
|
||||
directives := make([]IDirective, 0)
|
||||
directives = append(directives, lb.Directives...)
|
||||
return directives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) FindDirectives(directiveName string) []IDirective {
|
||||
directives := make([]IDirective, 0)
|
||||
for _, directive := range lb.GetDirectives() {
|
||||
if directive.GetName() == directiveName {
|
||||
directives = append(directives, directive)
|
||||
}
|
||||
if directive.GetBlock() != nil {
|
||||
directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...)
|
||||
}
|
||||
}
|
||||
|
||||
return directives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetCodeBlock() string {
|
||||
return lb.LuaCode
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetBlock() IBlock {
|
||||
return lb
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetComment() string {
|
||||
return lb.Comment
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) RemoveDirective(key string, params []string) {
|
||||
directives := lb.Directives
|
||||
var newDirectives []IDirective
|
||||
for _, dir := range directives {
|
||||
if dir.GetName() == key {
|
||||
if len(params) > 0 {
|
||||
oldParams := dir.GetParameters()
|
||||
if oldParams[0] == params[0] {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
newDirectives = append(newDirectives, dir)
|
||||
}
|
||||
lb.Directives = newDirectives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) UpdateDirective(key string, params []string) {
|
||||
if key == "" || len(params) == 0 {
|
||||
return
|
||||
}
|
||||
directives := lb.Directives
|
||||
index := -1
|
||||
for i, dir := range directives {
|
||||
if dir.GetName() == key {
|
||||
if IsRepeatKey(key) {
|
||||
oldParams := dir.GetParameters()
|
||||
if !(len(oldParams) > 0 && oldParams[0] == params[0]) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
newDirective := &Directive{
|
||||
Name: key,
|
||||
Parameters: params,
|
||||
}
|
||||
if index > -1 {
|
||||
directives[index] = newDirective
|
||||
} else {
|
||||
directives = append(directives, newDirective)
|
||||
}
|
||||
lb.Directives = directives
|
||||
}
|
||||
|
||||
func (lb *LuaBlock) GetLine() int {
|
||||
return lb.Line
|
||||
}
|
||||
@@ -11,6 +11,10 @@ type Server struct {
|
||||
Line int
|
||||
}
|
||||
|
||||
func (s *Server) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewServer(directive IDirective) (*Server, error) {
|
||||
server := &Server{}
|
||||
if block := directive.GetBlock(); block != nil {
|
||||
|
||||
@@ -7,6 +7,7 @@ type IBlock interface {
|
||||
UpdateDirective(name string, params []string)
|
||||
GetComment() string
|
||||
GetLine() int
|
||||
GetCodeBlock() string
|
||||
}
|
||||
|
||||
type IDirective interface {
|
||||
|
||||
@@ -12,6 +12,10 @@ type Upstream struct {
|
||||
Line int
|
||||
}
|
||||
|
||||
func (us *Upstream) GetCodeBlock() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (us *Upstream) GetName() string {
|
||||
return "upstream"
|
||||
}
|
||||
|
||||
@@ -65,6 +65,21 @@ func DumpDirective(d components.IDirective, style *Style) string {
|
||||
|
||||
func DumpBlock(b components.IBlock, style *Style, startLine int) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if b.GetCodeBlock() != "" {
|
||||
luaLines := strings.Split(b.GetCodeBlock(), "\n")
|
||||
for i, line := range luaLines {
|
||||
if strings.Replace(line, " ", "", -1) == "" {
|
||||
continue
|
||||
}
|
||||
buf.WriteString(line)
|
||||
if i != len(luaLines)-1 {
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
line := startLine
|
||||
if b.GetLine() > startLine {
|
||||
for i := 0; i < b.GetLine()-startLine; i++ {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
|
||||
)
|
||||
|
||||
func GetConfig(path string) (*components.Config, error) {
|
||||
p, err := parser.NewParser(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Parse(), nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNginx(t *testing.T) {
|
||||
//config, err := GetConfig("/opt/1Panel/data/apps/nginx/nginx-1/conf/conf.d/word-1.conf")
|
||||
config, err := GetConfig("/opt/1Panel/data/apps/nginx/nginx-new/conf/conf.d/1panel.cloud.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//server := config.FindServers()[0]
|
||||
//fmt.Println(server)
|
||||
//serverD := config.FindServers()[0]
|
||||
//serverD.AddListen("8989", false)
|
||||
|
||||
fmt.Println(DumpConfig(config, IndentedStyle))
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
@@ -18,6 +14,7 @@ const (
|
||||
Comment
|
||||
Illegal
|
||||
Regex
|
||||
LuaCode
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,33 +43,13 @@ type Flag struct {
|
||||
Column int
|
||||
}
|
||||
|
||||
func (t Flag) String() string {
|
||||
return fmt.Sprintf("{Type:%s,Literal:\"%s\",Line:%d,Column:%d}", t.Type, t.Literal, t.Line, t.Column)
|
||||
}
|
||||
|
||||
func (t Flag) Lit(literal string) Flag {
|
||||
t.Literal = literal
|
||||
return t
|
||||
}
|
||||
|
||||
func (t Flag) EqualTo(t2 Flag) bool {
|
||||
return t.Type == t2.Type && t.Literal == t2.Literal
|
||||
}
|
||||
|
||||
type Flags []Flag
|
||||
|
||||
func (fs Flags) EqualTo(flags Flags) bool {
|
||||
if len(fs) != len(flags) {
|
||||
return false
|
||||
}
|
||||
for i, t := range fs {
|
||||
if !t.EqualTo(flags[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t Flag) Is(typ Type) bool {
|
||||
return t.Type == typ
|
||||
}
|
||||
|
||||
@@ -3,17 +3,18 @@ package parser
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type lexer struct {
|
||||
reader *bufio.Reader
|
||||
file string
|
||||
line int
|
||||
column int
|
||||
Latest flag.Flag
|
||||
reader *bufio.Reader
|
||||
file string
|
||||
line int
|
||||
column int
|
||||
inLuaBlock bool
|
||||
Latest flag.Flag
|
||||
}
|
||||
|
||||
func lex(content string) *lexer {
|
||||
@@ -45,6 +46,11 @@ func (s *lexer) scan() flag.Flag {
|
||||
//}
|
||||
|
||||
func (s *lexer) getNextFlag() flag.Flag {
|
||||
if s.inLuaBlock {
|
||||
s.inLuaBlock = false
|
||||
flag := s.scanLuaCode()
|
||||
return flag
|
||||
}
|
||||
retoFlag:
|
||||
ch := s.peek()
|
||||
switch {
|
||||
@@ -56,6 +62,9 @@ retoFlag:
|
||||
case ch == ';':
|
||||
return s.NewToken(flag.Semicolon).Lit(string(s.read()))
|
||||
case ch == '{':
|
||||
if isLuaBlock(s.Latest) {
|
||||
s.inLuaBlock = true
|
||||
}
|
||||
return s.NewToken(flag.BlockStart).Lit(string(s.read()))
|
||||
case ch == '}':
|
||||
return s.NewToken(flag.BlockEnd).Lit(string(s.read()))
|
||||
@@ -70,6 +79,35 @@ retoFlag:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lexer) scanLuaCode() flag.Flag {
|
||||
ret := s.NewToken(flag.LuaCode)
|
||||
stack := make([]rune, 0, 50)
|
||||
code := strings.Builder{}
|
||||
|
||||
for {
|
||||
ch := s.read()
|
||||
if ch == rune(flag.EOF) {
|
||||
panic("unexpected end of file while scanning a string, maybe an unclosed lua code?")
|
||||
}
|
||||
if ch == '#' {
|
||||
code.WriteRune(ch)
|
||||
code.WriteString(s.readUntil(isEndOfLine))
|
||||
continue
|
||||
} else if ch == '}' {
|
||||
if len(stack) == 0 {
|
||||
_ = s.reader.UnreadRune()
|
||||
return ret.Lit(strings.TrimRight(strings.Trim(code.String(), "\n"), "\n "))
|
||||
}
|
||||
if stack[len(stack)-1] == '{' {
|
||||
stack = stack[0 : len(stack)-1]
|
||||
}
|
||||
} else if ch == '{' {
|
||||
stack = append(stack, ch)
|
||||
}
|
||||
code.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lexer) peek() rune {
|
||||
r, _, _ := s.reader.ReadRune()
|
||||
_ = s.reader.UnreadRune()
|
||||
@@ -128,7 +166,7 @@ func (s *lexer) scanComment() flag.Flag {
|
||||
func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
|
||||
var buf bytes.Buffer
|
||||
tok := s.NewToken(flag.QuotedString)
|
||||
buf.WriteRune(s.read()) //consume delimiter
|
||||
_, _ = buf.WriteRune(s.read())
|
||||
for {
|
||||
ch := s.read()
|
||||
|
||||
@@ -136,29 +174,13 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
|
||||
panic("unexpected end of file while scanning a string, maybe an unclosed quote?")
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
if needsEscape(s.peek(), delimiter) {
|
||||
nextch := s.read()
|
||||
switch nextch {
|
||||
case 'n':
|
||||
fmt.Println("n")
|
||||
buf.WriteRune('\n')
|
||||
case 'r':
|
||||
fmt.Println("r")
|
||||
buf.WriteRune('\r')
|
||||
case 't':
|
||||
fmt.Println("t")
|
||||
buf.WriteRune('\t')
|
||||
case '\\':
|
||||
buf.WriteRune('\\')
|
||||
default:
|
||||
buf.WriteRune('\\')
|
||||
buf.WriteRune(nextch)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ch == '\\' && (s.peek() == delimiter) {
|
||||
buf.WriteRune(ch)
|
||||
buf.WriteRune(s.read())
|
||||
continue
|
||||
}
|
||||
buf.WriteRune(ch)
|
||||
|
||||
_, _ = buf.WriteRune(ch)
|
||||
if ch == delimiter {
|
||||
break
|
||||
}
|
||||
@@ -168,7 +190,31 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
|
||||
}
|
||||
|
||||
func (s *lexer) scanKeyword() flag.Flag {
|
||||
return s.NewToken(flag.Keyword).Lit(s.readUntil(isKeywordTerminator))
|
||||
var buf bytes.Buffer
|
||||
tok := s.NewToken(flag.Keyword)
|
||||
prev := s.read()
|
||||
buf.WriteRune(prev)
|
||||
for {
|
||||
ch := s.peek()
|
||||
|
||||
if isSpace(ch) || isEOF(ch) || ch == ';' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '{' {
|
||||
if prev == '$' {
|
||||
buf.WriteString(s.readUntil(func(r rune) bool {
|
||||
return r == '}'
|
||||
}))
|
||||
buf.WriteRune(s.read()) //consume latest '}'
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
buf.WriteRune(s.read())
|
||||
}
|
||||
|
||||
return tok.Lit(buf.String())
|
||||
}
|
||||
|
||||
func (s *lexer) scanVariable() flag.Flag {
|
||||
@@ -198,10 +244,6 @@ func isKeywordTerminator(ch rune) bool {
|
||||
return isSpace(ch) || isEndOfLine(ch) || ch == '{' || ch == ';'
|
||||
}
|
||||
|
||||
func needsEscape(ch, delimiter rune) bool {
|
||||
return ch == delimiter || ch == 'n' || ch == 't' || ch == '\\' || ch == 'r'
|
||||
}
|
||||
|
||||
func isSpace(ch rune) bool {
|
||||
return ch == ' ' || ch == '\t' || isEndOfLine(ch)
|
||||
}
|
||||
@@ -213,3 +255,7 @@ func isEOF(ch rune) bool {
|
||||
func isEndOfLine(ch rune) bool {
|
||||
return ch == '\r' || ch == '\n'
|
||||
}
|
||||
|
||||
func isLuaBlock(t flag.Flag) bool {
|
||||
return t.Type == flag.Keyword && strings.HasSuffix(t.Literal, "_by_lua_block")
|
||||
}
|
||||
|
||||
@@ -2,17 +2,19 @@ package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
components "github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
lexer *lexer
|
||||
currentToken flag.Flag
|
||||
followingToken flag.Flag
|
||||
blockWrappers map[string]func(*components.Directive) components.IDirective
|
||||
blockWrappers map[string]func(*components.Directive) (components.IDirective, error)
|
||||
directiveWrappers map[string]func(*components.Directive) components.IDirective
|
||||
}
|
||||
|
||||
@@ -39,18 +41,21 @@ func NewParserFromLexer(lexer *lexer) *Parser {
|
||||
parser.nextToken()
|
||||
parser.nextToken()
|
||||
|
||||
parser.blockWrappers = map[string]func(*components.Directive) components.IDirective{
|
||||
"http": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapHttp(directive)
|
||||
parser.blockWrappers = map[string]func(*components.Directive) (components.IDirective, error){
|
||||
"http": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapHttp(directive), nil
|
||||
},
|
||||
"server": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapServer(directive)
|
||||
"server": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapServer(directive), nil
|
||||
},
|
||||
"location": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapLocation(directive)
|
||||
"location": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapLocation(directive), nil
|
||||
},
|
||||
"upstream": func(directive *components.Directive) components.IDirective {
|
||||
return parser.wrapUpstream(directive)
|
||||
"upstream": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapUpstream(directive), nil
|
||||
},
|
||||
"_by_lua_block": func(directive *components.Directive) (components.IDirective, error) {
|
||||
return parser.wrapLuaBlock(directive)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -76,14 +81,19 @@ func (p *Parser) followingTokenIs(t flag.Type) bool {
|
||||
return p.followingToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) Parse() *components.Config {
|
||||
return &components.Config{
|
||||
FilePath: p.lexer.file,
|
||||
Block: p.parseBlock(),
|
||||
func (p *Parser) Parse() (*components.Config, error) {
|
||||
parsedBlock, err := p.parseBlock(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &components.Config{
|
||||
FilePath: p.lexer.file,
|
||||
Block: parsedBlock,
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (p *Parser) parseBlock() *components.Block {
|
||||
func (p *Parser) parseBlock(inBlock bool) (*components.Block, error) {
|
||||
context := &components.Block{
|
||||
Comment: "",
|
||||
Directives: make([]components.IDirective, 0),
|
||||
@@ -93,10 +103,22 @@ func (p *Parser) parseBlock() *components.Block {
|
||||
parsingloop:
|
||||
for {
|
||||
switch {
|
||||
case p.curTokenIs(flag.EOF) || p.curTokenIs(flag.BlockEnd):
|
||||
case p.curTokenIs(flag.EOF):
|
||||
if inBlock {
|
||||
return nil, errors.New("unexpected eof in block")
|
||||
}
|
||||
break parsingloop
|
||||
case p.curTokenIs(flag.BlockEnd):
|
||||
break parsingloop
|
||||
case p.curTokenIs(flag.LuaCode):
|
||||
context.IsLuaBlock = true
|
||||
context.LiteralCode = p.currentToken.Literal
|
||||
case p.curTokenIs(flag.Keyword):
|
||||
context.Directives = append(context.Directives, p.parseStatement())
|
||||
s, err := p.parseStatement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context.Directives = append(context.Directives, s)
|
||||
case p.curTokenIs(flag.Comment):
|
||||
context.Directives = append(context.Directives, &components.Comment{
|
||||
Detail: p.currentToken.Literal,
|
||||
@@ -106,10 +128,10 @@ parsingloop:
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return context
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() components.IDirective {
|
||||
func (p *Parser) parseStatement() (components.IDirective, error) {
|
||||
d := &components.Directive{
|
||||
Name: p.currentToken.Literal,
|
||||
Line: p.currentToken.Line,
|
||||
@@ -121,30 +143,38 @@ func (p *Parser) parseStatement() components.IDirective {
|
||||
|
||||
if p.curTokenIs(flag.Semicolon) {
|
||||
if dw, ok := p.directiveWrappers[d.Name]; ok {
|
||||
return dw(d)
|
||||
return dw(d), nil
|
||||
}
|
||||
if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line {
|
||||
d.Comment = p.followingToken.Literal
|
||||
p.nextToken()
|
||||
}
|
||||
return d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
if p.curTokenIs(flag.BlockStart) {
|
||||
|
||||
inLineComment := ""
|
||||
if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line {
|
||||
inLineComment = p.followingToken.Literal
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
}
|
||||
block := p.parseBlock()
|
||||
block, err := p.parseBlock(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block.Comment = inLineComment
|
||||
d.Block = block
|
||||
|
||||
if strings.HasSuffix(d.Name, "_by_lua_block") {
|
||||
return p.blockWrappers["_by_lua_block"](d)
|
||||
}
|
||||
|
||||
if bw, ok := p.blockWrappers[d.Name]; ok {
|
||||
return bw(d)
|
||||
}
|
||||
return d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unexpected token %s (%s) on line %d, column %d", p.currentToken.Type.String(), p.currentToken.Literal, p.currentToken.Line, p.currentToken.Column))
|
||||
@@ -169,6 +199,10 @@ func (p *Parser) wrapHttp(directive *components.Directive) *components.Http {
|
||||
return h
|
||||
}
|
||||
|
||||
func (p *Parser) wrapLuaBlock(directive *components.Directive) (*components.LuaBlock, error) {
|
||||
return components.NewLuaBlock(directive)
|
||||
}
|
||||
|
||||
func (p *Parser) parseUpstreamServer(directive *components.Directive) *components.UpstreamServer {
|
||||
return components.NewUpstreamServer(directive)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package ssl
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/go-acme/lego/v4/acme"
|
||||
"github.com/go-acme/lego/v4/acme/api"
|
||||
@@ -21,7 +23,6 @@ import (
|
||||
"github.com/go-acme/lego/v4/providers/http/webroot"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AcmeUser struct {
|
||||
@@ -204,23 +205,6 @@ func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.PrivateKey) (
|
||||
return *certificates, nil
|
||||
}
|
||||
|
||||
func (c *AcmeClient) RenewSSL(certUrl string) (certificate.Resource, error) {
|
||||
certificates, err := c.Client.Certificate.Get(certUrl, true)
|
||||
if err != nil {
|
||||
return certificate.Resource{}, err
|
||||
}
|
||||
certificates, err = c.Client.Certificate.RenewWithOptions(*certificates, &certificate.RenewOptions{
|
||||
Bundle: true,
|
||||
PreferredChain: "",
|
||||
MustStaple: true,
|
||||
})
|
||||
if err != nil {
|
||||
return certificate.Resource{}, err
|
||||
}
|
||||
|
||||
return *certificates, nil
|
||||
}
|
||||
|
||||
type Resolve struct {
|
||||
Key string
|
||||
Value string
|
||||
|
||||
@@ -3,15 +3,16 @@ package websocket
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WsInput struct {
|
||||
@@ -86,18 +87,6 @@ type processConnect struct {
|
||||
|
||||
type ProcessConnects []processConnect
|
||||
|
||||
func (p ProcessConnects) Len() int {
|
||||
return len(p)
|
||||
}
|
||||
|
||||
func (p ProcessConnects) Less(i, j int) bool {
|
||||
return p[i].PID < p[j].PID
|
||||
}
|
||||
|
||||
func (p ProcessConnects) Swap(i, j int) {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
|
||||
type sshSession struct {
|
||||
Username string `json:"username"`
|
||||
PID int32 `json:"PID"`
|
||||
|
||||
+47
-62
@@ -1,5 +1,5 @@
|
||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@@ -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": [
|
||||
@@ -4954,45 +4979,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 postgresql 数据库列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Database Postgresql"
|
||||
],
|
||||
"summary": "List postgresql database names",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.PostgresqlOption"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -16867,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": [
|
||||
@@ -17203,26 +17209,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PostgresqlOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RecordSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -19026,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": [
|
||||
@@ -4947,45 +4972,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取 postgresql 数据库列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Database Postgresql"
|
||||
],
|
||||
"summary": "List postgresql database names",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.PostgresqlOption"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/pg/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -16860,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": [
|
||||
@@ -17196,26 +17202,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.PostgresqlOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RecordSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -19019,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:
|
||||
@@ -2119,19 +2132,6 @@ definitions:
|
||||
- from
|
||||
- type
|
||||
type: object
|
||||
dto.PostgresqlOption:
|
||||
properties:
|
||||
database:
|
||||
type: string
|
||||
from:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
dto.RecordSearch:
|
||||
properties:
|
||||
detailName:
|
||||
@@ -3332,7 +3332,6 @@ definitions:
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- content
|
||||
- path
|
||||
type: object
|
||||
request.FileMove:
|
||||
@@ -7425,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:
|
||||
@@ -8140,30 +8154,6 @@ paths:
|
||||
[description]
|
||||
formatZH: postgresql 数据库 [name] 描述信息修改 [description]
|
||||
paramKeys: []
|
||||
/databases/pg/options:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取 postgresql 数据库列表
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.PageInfo'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.PostgresqlOption'
|
||||
type: array
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: List postgresql database names
|
||||
tags:
|
||||
- Database Postgresql
|
||||
/databases/pg/password:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
+5
-5
@@ -12,11 +12,11 @@
|
||||
|
||||
1Panel is a modern and Open-Source linux server operation and management panel, the functions and advantages of 1Panel include:
|
||||
|
||||
- **Quick website building**: Deeply integrated with Wordpress and [Halo](https://github.com/halo-dev/halo/), with one-click solutions for domain name binding, SSL certificate configuration, and more;
|
||||
- **Efficient management**: Easily manage Linux servers through the web interface, including application management, host monitoring, file management, database management, container management, and more;
|
||||
- **Secure and reliable**: Minimal vulnerability exposure, with firewall and security audit functions provided;
|
||||
- **One-click backup**: Support for one-click backup and restore, with backup data stored in the cloud and never lost;
|
||||
- **App Store**: Curate a variety of high-quality open-source tools and application software, enabling you to easily install and upgrade them.
|
||||
- **Efficient Management**: Users can easily manage Linux servers through a web graphical interface, achieving functions such as host monitoring, file management, database management, and container management.
|
||||
- **Rapid Website Building**: Deep integration of open-source website building software WordPress and [Halo](https://github.com/halo-dev/halo/), with operations such as domain binding and SSL certificate configuration completed with one click.
|
||||
- **Application Store**: Curates a variety of high-quality open-source tools and application software, assisting users in easy installation and upgrades.
|
||||
- **Security and Reliability**: Based on container management and application deployment, minimizing vulnerability exposure, while providing features such as firewall and log auditing.
|
||||
- **One-Click Backup**: Supports one-click backup and restoration, allowing users to backup data to various cloud storage media, ensuring data is never lost.
|
||||
|
||||
## UI Display
|
||||
|
||||
|
||||
+5
-5
@@ -12,11 +12,11 @@
|
||||
|
||||
1Panel はモダンでオープンソースの Linux サーバー操作と管理パネルです:
|
||||
|
||||
- **素早いウェブサイト構築**: Wordpress および [Halo](https://github.com/halo-dev/halo/) と深く統合されており、ドメイン名のバインディング、SSL 証明書の設定など、ワンクリックで解決できます;
|
||||
- **効率的なマネジメント**: アプリケーション管理、ホスト監視、ファイル管理、データベース管理、コンテナ管理など、web インターフェイスを通じて Linux サーバーを簡単に管理できます;
|
||||
- **安全性と信頼性**: ファイアウォールとセキュリティ監査機能により、脆弱性の露出を最小限に抑えます;
|
||||
- **ワンクリックバックアップ**: ワンクリックでのバックアップと復元をサポートし、バックアップデータはクラウドに保存され、失われることはありません;
|
||||
- **アプリストア**: さまざまな種類の高品質なオープンソースツールとアプリケーションソフトウェアを厳選し、インストールとアップグレードを容易にサポートします。
|
||||
- **効率的管理**:ユーザーは、Web グラフィカルインターフェースを使用して、Linux サーバーを簡単に管理し、ホストの監視、ファイル管理、データベース管理、コンテナ管理などの機能を実現できます。
|
||||
- **迅速なウェブサイト構築**:オープンソースのウェブサイト構築ソフトウェアであるWordPressと[Halo](https://github.com/halo-dev/halo/)を深く統合し、ドメインのバインディング、SSL 証明書の設定などをワンクリックで完了します。
|
||||
- **アプリストア**:さまざまな高品質のオープンソースツールやアプリケーションソフトウェアを厳選して提供し、ユーザーが簡単にインストールおよびアップグレードできるよう支援します。
|
||||
- **安全性と信頼性**:コンテナの管理とアプリケーションのデプロイに基づいて、最小限の脆弱性露出面を実現し、同時にファイアウォールやログ監査などの機能を提供します。
|
||||
- **ワンクリックバックアップ**:ワンクリックでバックアップと復元をサポートし、ユーザーはデータをさまざまなクラウドストレージメディアにバックアップし、永久に失うことはありません。
|
||||
|
||||
## UI ディスプレイ
|
||||
|
||||
|
||||
+5
-5
@@ -13,11 +13,11 @@
|
||||
|
||||
1Panel 是一個現代化、開源的 Linux 服務器運維管理面板。1Panel 的功能和優勢包括:
|
||||
|
||||
- **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名綁定、SSL 證書配置等一鍵搞定;
|
||||
- **高效管理**:通過 Web 端輕鬆管理 Linux 服務器,包括主機監控、文件管理、數據庫管理、容器管理等;
|
||||
- **安全可靠**:基於容器來管理和部署應用,最小漏洞暴露面,提供防火牆和日誌審計等功能;
|
||||
- **一鍵備份**:支持一鍵備份和恢復,備份數據到各類雲端存儲,永不丟失;
|
||||
- **應用商店**:精選各類高質量開源工具和應用軟件,助力您輕鬆安裝和升級。
|
||||
- **高效管理**:使用者可以透過網頁圖形介面輕鬆管理 Linux 伺服器,實現主機監控、檔案管理、資料庫管理、容器管理等功能;
|
||||
- **快速建站**:深度整合開源建站軟體 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名綁定、SSL 憑證配置等操作一鍵搞定;
|
||||
- **應用商店**:精選上架各類高品質的開源工具和應用軟體,協助使用者輕鬆安裝並升級;
|
||||
- **安全可靠**:基於容器管理並部署應用,實現最小的漏洞暴露面,同時提供防火牆和日誌審計等功能;
|
||||
- **一鍵備份**:支援一鍵備份和恢復,使用者可以將資料備份到各類雲端儲存媒介,永不遺失。
|
||||
|
||||
## UI 展示
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ class RequestHttp {
|
||||
...config.headers,
|
||||
};
|
||||
if (config.url === '/auth/login' || config.url === '/auth/mfalogin') {
|
||||
let entrace = Base64.encode(globalStore.entrance);
|
||||
config.headers.EntranceCode = entrace;
|
||||
let entrance = Base64.encode(globalStore.entrance);
|
||||
config.headers.EntranceCode = entrance;
|
||||
}
|
||||
return {
|
||||
...config,
|
||||
|
||||
@@ -245,7 +245,7 @@ export namespace Container {
|
||||
path: string;
|
||||
template: number;
|
||||
}
|
||||
export interface ComposeOpration {
|
||||
export interface ComposeOperation {
|
||||
name: string;
|
||||
operation: string;
|
||||
path: string;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -156,7 +156,7 @@ export const upCompose = (params: Container.ComposeCreate) => {
|
||||
export const testCompose = (params: Container.ComposeCreate) => {
|
||||
return http.post<boolean>(`/containers/compose/test`, params);
|
||||
};
|
||||
export const composeOperator = (params: Container.ComposeOpration) => {
|
||||
export const composeOperator = (params: Container.ComposeOperation) => {
|
||||
return http.post(`/containers/compose/operate`, params);
|
||||
};
|
||||
export const composeUpdate = (params: Container.ComposeUpdate) => {
|
||||
|
||||
@@ -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}`);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Anhui": "安徽",
|
||||
"Beijing": "北京",
|
||||
"Fujian": "福建",
|
||||
"Gansu": "甘肃",
|
||||
"Guangdong": "广东",
|
||||
"Guangxi": "广西",
|
||||
"Guizhou": "贵州",
|
||||
"Hainan": "海南",
|
||||
"Hebei": "河北",
|
||||
"Henan": "河南",
|
||||
"Heilongjiang": "黑龙江",
|
||||
"Hubei": "湖北",
|
||||
"Hunan": "湖南",
|
||||
"Jilin": "吉林",
|
||||
"Jiangsu": "江苏",
|
||||
"Jiangxi": "江西",
|
||||
"Liaoning": "辽宁",
|
||||
"Inner Mongolia": "内蒙古",
|
||||
"Ningxia": "宁夏",
|
||||
"Qinghai": "青海",
|
||||
"Shandong": "山东",
|
||||
"Shanxi": "山西",
|
||||
"Shaanxi": "陕西",
|
||||
"Shanghai": "上海",
|
||||
"Sichuan": "四川",
|
||||
"Tianjin": "天津",
|
||||
"Tibet": "西藏",
|
||||
"Xinjiang": "新疆",
|
||||
"Yunnan": "云南",
|
||||
"Zhejiang": "浙江",
|
||||
"Chongqing": "重庆",
|
||||
"HongKong": "香港",
|
||||
"Macao": "澳门",
|
||||
"Taiwan": "台湾"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
{
|
||||
"Afghanistan": "阿富汗",
|
||||
"Albania": "阿尔巴尼亚",
|
||||
"Algeria": "阿尔及利亚",
|
||||
"Angola": "安哥拉",
|
||||
"Argentina": "阿根廷",
|
||||
"Armenia": "亚美尼亚",
|
||||
"Australia": "澳大利亚",
|
||||
"Austria": "奥地利",
|
||||
"Azerbaijan": "阿塞拜疆",
|
||||
"Bahamas": "巴哈马",
|
||||
"Bahrain": "巴林",
|
||||
"Bangladesh": "孟加拉国",
|
||||
"Belarus": "白俄罗斯",
|
||||
"Belgium": "比利时",
|
||||
"Belize": "伯利兹",
|
||||
"Benin": "贝宁",
|
||||
"Bhutan": "不丹",
|
||||
"Bolivia": "玻利维亚",
|
||||
"Bosnia and Herz.": "波斯尼亚和黑塞哥维那",
|
||||
"Botswana": "博茨瓦纳",
|
||||
"Brazil": "巴西",
|
||||
"British Virgin Islands": "英属维京群岛",
|
||||
"Brunei": "文莱",
|
||||
"Bulgaria": "保加利亚",
|
||||
"Burkina Faso": "布基纳法索",
|
||||
"Burundi": "布隆迪",
|
||||
"Cambodia": "柬埔寨",
|
||||
"Cameroon": "喀麦隆",
|
||||
"Canada": "加拿大",
|
||||
"Cape Verde": "佛得角",
|
||||
"Cayman Islands": "开曼群岛",
|
||||
"Central African Rep.": "中非共和国",
|
||||
"Chad": "乍得",
|
||||
"Chile": "智利",
|
||||
"China": "中国",
|
||||
"Colombia": "哥伦比亚",
|
||||
"Comoros": "科摩罗",
|
||||
"Congo": "刚果",
|
||||
"Costa Rica": "哥斯达黎加",
|
||||
"Croatia": "克罗地亚",
|
||||
"Cuba": "古巴",
|
||||
"Cyprus": "塞浦路斯",
|
||||
"Czech Rep.": "捷克共和国",
|
||||
"Côte d'Ivoire": "科特迪瓦",
|
||||
"Dem. Rep. Congo": "刚果民主共和国",
|
||||
"Dem. Rep. Korea": "朝鲜",
|
||||
"Denmark": "丹麦",
|
||||
"Djibouti": "吉布提",
|
||||
"Dominican Rep.": "多米尼加共和国",
|
||||
"Ecuador": "厄瓜多尔",
|
||||
"Egypt": "埃及",
|
||||
"El Salvador": "萨尔瓦多",
|
||||
"Equatorial Guinea": "赤道几内亚",
|
||||
"Eritrea": "厄立特里亚",
|
||||
"Estonia": "爱沙尼亚",
|
||||
"Ethiopia": "埃塞俄比亚",
|
||||
"Falkland Is.": "福克兰群岛",
|
||||
"Fiji": "斐济",
|
||||
"Finland": "芬兰",
|
||||
"Fr. S. Antarctic Lands": "所罗门群岛",
|
||||
"France": "法国",
|
||||
"Gabon": "加蓬",
|
||||
"Gambia": "冈比亚",
|
||||
"Georgia": "格鲁吉亚",
|
||||
"Germany": "德国",
|
||||
"Ghana": "加纳",
|
||||
"Greece": "希腊",
|
||||
"Greenland": "格陵兰",
|
||||
"Guatemala": "危地马拉",
|
||||
"Guinea": "几内亚",
|
||||
"Guinea-Bissau": "几内亚比绍",
|
||||
"Guyana": "圭亚那",
|
||||
"Haiti": "海地",
|
||||
"Honduras": "洪都拉斯",
|
||||
"Hungary": "匈牙利",
|
||||
"Iceland": "冰岛",
|
||||
"India": "印度",
|
||||
"Indonesia": "印度尼西亚",
|
||||
"Iran": "伊朗",
|
||||
"Iraq": "伊拉克",
|
||||
"Ireland": "爱尔兰",
|
||||
"Isle of Man": "马恩岛",
|
||||
"Israel": "以色列",
|
||||
"Italy": "意大利",
|
||||
"Jamaica": "牙买加",
|
||||
"Japan": "日本",
|
||||
"Jordan": "约旦",
|
||||
"Kazakhstan": "哈萨克斯坦",
|
||||
"Kenya": "肯尼亚",
|
||||
"Korea": "韩国",
|
||||
"Kuwait": "科威特",
|
||||
"Kyrgyzstan": "吉尔吉斯斯坦",
|
||||
"Lao PDR": "老挝",
|
||||
"Latvia": "拉脱维亚",
|
||||
"Lebanon": "黎巴嫩",
|
||||
"Lesotho": "莱索托",
|
||||
"Liberia": "利比里亚",
|
||||
"Libya": "利比亚",
|
||||
"Lithuania": "立陶宛",
|
||||
"Luxembourg": "卢森堡",
|
||||
"Macedonia": "马其顿",
|
||||
"Madagascar": "马达加斯加",
|
||||
"Malawi": "马拉维",
|
||||
"Malaysia": "马来西亚",
|
||||
"Maldives": "马尔代夫",
|
||||
"Mali": "马里",
|
||||
"Malta": "马耳他",
|
||||
"Mauritania": "毛利塔尼亚",
|
||||
"Mauritius": "毛里求斯",
|
||||
"Mexico": "墨西哥",
|
||||
"Moldova": "摩尔多瓦",
|
||||
"Monaco": "摩纳哥",
|
||||
"Mongolia": "蒙古",
|
||||
"Montenegro": "黑山共和国",
|
||||
"Morocco": "摩洛哥",
|
||||
"Mozambique": "莫桑比克",
|
||||
"Myanmar": "缅甸",
|
||||
"Namibia": "纳米比亚",
|
||||
"Nepal": "尼泊尔",
|
||||
"Netherlands": "荷兰",
|
||||
"New Caledonia": "新喀里多尼亚",
|
||||
"New Zealand": "新西兰",
|
||||
"Nicaragua": "尼加拉瓜",
|
||||
"Niger": "尼日尔",
|
||||
"Nigeria": "尼日利亚",
|
||||
"Norway": "挪威",
|
||||
"Oman": "阿曼",
|
||||
"Pakistan": "巴基斯坦",
|
||||
"Panama": "巴拿马",
|
||||
"Papua New Guinea": "巴布亚新几内亚",
|
||||
"Paraguay": "巴拉圭",
|
||||
"Peru": "秘鲁",
|
||||
"Philippines": "菲律宾",
|
||||
"Poland": "波兰",
|
||||
"Portugal": "葡萄牙",
|
||||
"Puerto Rico": "波多黎各",
|
||||
"Qatar": "卡塔尔",
|
||||
"Reunion": "留尼旺",
|
||||
"Romania": "罗马尼亚",
|
||||
"Russia": "俄罗斯",
|
||||
"Rwanda": "卢旺达",
|
||||
"S. Geo. and S. Sandw. Is.": "南乔治亚和南桑威奇群岛",
|
||||
"S. Sudan": "南苏丹",
|
||||
"San Marino": "圣马力诺",
|
||||
"Saudi Arabia": "沙特阿拉伯",
|
||||
"Senegal": "塞内加尔",
|
||||
"Serbia": "塞尔维亚",
|
||||
"Sierra Leone": "塞拉利昂",
|
||||
"Singapore": "新加坡",
|
||||
"Slovakia": "斯洛伐克",
|
||||
"Slovenia": "斯洛文尼亚",
|
||||
"Solomon Is.": "所罗门群岛",
|
||||
"Somalia": "索马里",
|
||||
"South Africa": "南非",
|
||||
"Spain": "西班牙",
|
||||
"Sri Lanka": "斯里兰卡",
|
||||
"Sudan": "苏丹",
|
||||
"Suriname": "苏里南",
|
||||
"Swaziland": "斯威士兰",
|
||||
"Sweden": "瑞典",
|
||||
"Switzerland": "瑞士",
|
||||
"Syria": "叙利亚",
|
||||
"Taiwan": "中国台湾",
|
||||
"Tajikistan": "塔吉克斯坦",
|
||||
"Tanzania": "坦桑尼亚",
|
||||
"Thailand": "泰国",
|
||||
"Togo": "多哥",
|
||||
"Tonga": "汤加",
|
||||
"Trinidad and Tobago": "特立尼达和多巴哥",
|
||||
"Tunisia": "突尼斯",
|
||||
"Turkey": "土耳其",
|
||||
"Turkmenistan": "土库曼斯坦",
|
||||
"U.S. Virgin Islands": "美属维尔京群岛",
|
||||
"Uganda": "乌干达",
|
||||
"Ukraine": "乌克兰",
|
||||
"United Arab Emirates": "阿拉伯联合酋长国",
|
||||
"United Kingdom": "英国",
|
||||
"United States": "美国",
|
||||
"Uruguay": "乌拉圭",
|
||||
"Uzbekistan": "乌兹别克斯坦",
|
||||
"Vanuatu": "瓦努阿图",
|
||||
"Vatican City": "梵蒂冈城",
|
||||
"Venezuela": "委内瑞拉",
|
||||
"Vietnam": "越南",
|
||||
"W. Sahara": "西撒哈拉",
|
||||
"Yemen": "也门",
|
||||
"Yugoslavia": "南斯拉夫",
|
||||
"Zaire": "扎伊尔",
|
||||
"Zambia": "赞比亚",
|
||||
"Zimbabwe": "津巴布韦"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,7 +66,7 @@
|
||||
|
||||
<span class="warn" v-if="key === 'openresty' && (httpPort != 80 || httpsPort != 443)">
|
||||
<el-alert class="helper" type="error" :closable="false">
|
||||
{{ $t('website.openrestryHelper', [httpPort, httpsPort]) }}
|
||||
{{ $t('website.openrestyHelper', [httpPort, httpsPort]) }}
|
||||
</el-alert>
|
||||
</span>
|
||||
</div>
|
||||
@@ -76,15 +76,15 @@
|
||||
<LayoutContent :title="getTitle(key)" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div>
|
||||
<div class="flx-center">
|
||||
<span>{{ $t('app.checkInstalledWarn', [data.app]) }}</span>
|
||||
<span @click="goRouter(key)">
|
||||
<span @click="goRouter(key)" class="flx-align-center">
|
||||
<el-icon class="ml-2"><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import OpDialog from '@/components/del-dialog/index.vue';
|
||||
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
|
||||
import { handleBackup, handleRecover } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
|
||||
@@ -4,6 +4,8 @@ import RouterButton from './router-button/index.vue';
|
||||
import ComplexTable from './complex-table/index.vue';
|
||||
import ErrPrompt from './error-prompt/index.vue';
|
||||
import OpDialog from './del-dialog/index.vue';
|
||||
import TableSearch from './table-search/index.vue';
|
||||
import TableSetting from './table-setting/index.vue';
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import CopyButton from '@/components/copy-button/index.vue';
|
||||
export default {
|
||||
@@ -15,5 +17,7 @@ export default {
|
||||
app.component(OpDialog.name, OpDialog);
|
||||
app.component(Tooltip.name, Tooltip);
|
||||
app.component(CopyButton.name, CopyButton);
|
||||
app.component(TableSearch.name, TableSearch);
|
||||
app.component(TableSetting.name, TableSetting);
|
||||
},
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user