mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
184
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5fdff61ff | ||
|
|
4832081b82 | ||
|
|
ef5a13f87f | ||
|
|
e2bc8e800a | ||
|
|
85a397949e | ||
|
|
5cfdcedf9a | ||
|
|
c5bb8dab8c | ||
|
|
f1421ba7f9 | ||
|
|
9e44db53a7 | ||
|
|
4814a76c47 | ||
|
|
2ec4b2561c | ||
|
|
4efe7240ad | ||
|
|
ed13136221 | ||
|
|
735fc75e35 | ||
|
|
b8a52ae707 | ||
|
|
2501a9f78a | ||
|
|
5a8d6db43e | ||
|
|
a3312331d2 | ||
|
|
fbd0e836d7 | ||
|
|
4935d88301 | ||
|
|
8625979cc3 | ||
|
|
c77742e72e | ||
|
|
43a6b4b735 | ||
|
|
f4ee534101 | ||
|
|
c055a07e57 | ||
|
|
62bead5c50 | ||
|
|
9edafc13a3 | ||
|
|
e6ccf8be1f | ||
|
|
222089d56a | ||
|
|
96e48847d0 | ||
|
|
38b0c42555 | ||
|
|
2eae8274f6 | ||
|
|
0de79e537e | ||
|
|
e37b87d63e | ||
|
|
37a0329d4a | ||
|
|
e037b69f52 | ||
|
|
34498392dc | ||
|
|
1687b99526 | ||
|
|
0bf6db1cf8 | ||
|
|
0cd06803e2 | ||
|
|
45f6a415de | ||
|
|
3e3f0d9452 | ||
|
|
35785f1e40 | ||
|
|
437d6eb0fd | ||
|
|
1e2c85e771 | ||
|
|
ef075df3fe | ||
|
|
1e22769c8b | ||
|
|
0751326b46 | ||
|
|
db4973cfde | ||
|
|
0b47d692b8 | ||
|
|
672ef66be8 | ||
|
|
7ab005540b | ||
|
|
7c928735cd | ||
|
|
9ec1610f34 | ||
|
|
4f2a340212 | ||
|
|
e69fd3b388 | ||
|
|
6991c773da | ||
|
|
9b88979b69 | ||
|
|
7c9608727d | ||
|
|
62828c464e | ||
|
|
f1ed051135 | ||
|
|
33a0c2ba30 | ||
|
|
494da3c209 | ||
|
|
646fc4e678 | ||
|
|
cdbe24a523 | ||
|
|
10012c6148 | ||
|
|
bb4fd69f3a | ||
|
|
6ff0f14841 | ||
|
|
1088034a5c | ||
|
|
8a011b1cf9 | ||
|
|
e3eebc938a | ||
|
|
08da7802c6 | ||
|
|
79ca1b4d6b | ||
|
|
47119e508b | ||
|
|
13bb5e0d30 | ||
|
|
218c165c26 | ||
|
|
d6006d34c0 | ||
|
|
810ea03ac1 | ||
|
|
e046315616 | ||
|
|
0a7f1eb286 | ||
|
|
b0411b2df6 | ||
|
|
471b7d9657 | ||
|
|
1527c63d37 | ||
|
|
0ebeeaebd6 | ||
|
|
1ae5bef40d | ||
|
|
9b9fe5444d | ||
|
|
a5d853c61a | ||
|
|
6610a3debe | ||
|
|
6b244b2b03 | ||
|
|
0b26d70927 | ||
|
|
18faed79f3 | ||
|
|
b0cc1248f0 | ||
|
|
8013c7fd96 | ||
|
|
306082d625 | ||
|
|
821b8dfe63 | ||
|
|
8d88ed812c | ||
|
|
aa93a6d373 | ||
|
|
b4e8c8503c | ||
|
|
4de733e084 | ||
|
|
6c67dc6ed4 | ||
|
|
ba6991bfb6 | ||
|
|
33309d2d93 | ||
|
|
1b2208a4d0 | ||
|
|
e3fb97fc8c | ||
|
|
1ff5bf85e9 | ||
|
|
062b2f2ae6 | ||
|
|
44445aca93 | ||
|
|
94de830637 | ||
|
|
9160726127 | ||
|
|
3cd0c89c4c | ||
|
|
c27fcf18fc | ||
|
|
b1f20d125e | ||
|
|
cd43bff44d | ||
|
|
8cf8375911 | ||
|
|
ebc3195df4 | ||
|
|
ce69f6a142 | ||
|
|
f6777d51b5 | ||
|
|
d916f2ba6f | ||
|
|
0e159f944a | ||
|
|
329bb94d3c | ||
|
|
7f2f7fa195 | ||
|
|
67c79bcde4 | ||
|
|
d7d7c7fd73 | ||
|
|
94028d97cf | ||
|
|
cbfedb5d5e | ||
|
|
7784ee9575 | ||
|
|
29d5d82fa5 | ||
|
|
46f01032b8 | ||
|
|
341f45b4ef | ||
|
|
a0907592b2 | ||
|
|
825ebd19fb | ||
|
|
8f24432a01 | ||
|
|
6ce35b723b | ||
|
|
a33eabb4f0 | ||
|
|
34a6587ebc | ||
|
|
17020ffe9f | ||
|
|
0f9b0d5d82 | ||
|
|
be36103475 | ||
|
|
a3b5869c2b | ||
|
|
0807da41a5 | ||
|
|
ea6870e3c9 | ||
|
|
70b7daf23a | ||
|
|
1d01fa1e14 | ||
|
|
b643fa2ca3 | ||
|
|
77f4f64f0d | ||
|
|
3b697c7520 | ||
|
|
8ca6b9b6fa | ||
|
|
0b66be7a3a | ||
|
|
f9f68a975c | ||
|
|
ba63907a65 | ||
|
|
2687c91e65 | ||
|
|
eb82c168c5 | ||
|
|
21b1242a94 | ||
|
|
a6784772b9 | ||
|
|
cf4fcaa07f | ||
|
|
62b1bb3191 | ||
|
|
8ce066a9d4 | ||
|
|
7d8da406cf | ||
|
|
b3d6b2b0de | ||
|
|
21f24f2e10 | ||
|
|
fe717d993b | ||
|
|
703da8a09a | ||
|
|
a99f19471b | ||
|
|
ca3be03ae7 | ||
|
|
71ca8016e1 | ||
|
|
5633bdd41e | ||
|
|
ab11d7257c | ||
|
|
39b8de7ada | ||
|
|
c49c4b7863 | ||
|
|
d42df7184c | ||
|
|
e7f172520a | ||
|
|
dca29174ea | ||
|
|
94181538a9 | ||
|
|
e9cc7bf1cf | ||
|
|
fa1e945b8d | ||
|
|
94702a79a4 | ||
|
|
24f573f9fd | ||
|
|
7e4606eda5 | ||
|
|
73e118a3e5 | ||
|
|
514d68d395 | ||
|
|
d0bb07796b | ||
|
|
66a451fc1f | ||
|
|
c13e2a36cc | ||
|
|
2affd2bc79 |
+5
-1
@@ -1 +1,5 @@
|
||||
*.ts linguist-language=Go
|
||||
*.ts linguist-language=Go
|
||||
*.js linguist-language=Go
|
||||
*.css linguist-language=Go
|
||||
*.scss linguist-language=Go
|
||||
*.html linguist-language=Go
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 问题与讨论 / Questions & Discussions
|
||||
url: https://github.com/1Panel-dev/1Panel/discussions
|
||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题 / Raise questions about the installation, deployment, use and other aspects of the project.
|
||||
- name: 对 1Panel 项目有其他问题
|
||||
url: https://1panel.cn
|
||||
about: 如果想要进一步了解 1Panel 项目,欢迎发送邮件到 support@fit2cloud.com 进行提问。
|
||||
- name: 反馈安全问题 (Report Security Bug)
|
||||
url: mailto://support@fit2cloud.com
|
||||
about: 请通过邮箱 support@fit2cloud.com 反馈安全问题 (Please report security vulnerabilities to support@fit2cloud.com)
|
||||
url: https://bbs.fit2cloud.com/c/1p/7
|
||||
about: 如果你对 1Panel 有其他想要提问的,我们欢迎到我们的官方社区进行提问。
|
||||
@@ -1,12 +0,0 @@
|
||||
name: 问题咨询
|
||||
description: 提出针对本项目安装部署、使用及其他方面的相关问题
|
||||
title: "[QUESTION]"
|
||||
labels: question
|
||||
assignees: wanghe-fit2cloud
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "请描述您的问题"
|
||||
validations:
|
||||
required: true
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: 为 PR 添加标签
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUBTOKEN }}
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
uses: Yikun/hub-mirror-action@master
|
||||
with:
|
||||
src: 'github/1Panel-dev'
|
||||
dst: 'gitee/fit2cloud-xlab'
|
||||
dst: 'gitee/fit2cloud-feizhiyun'
|
||||
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_TOKEN }}
|
||||
static_list: "1Panel"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
name: Typos Check
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@master
|
||||
+4
-1
@@ -16,7 +16,8 @@ build/1panel
|
||||
*.factorypath
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea
|
||||
.idea/*
|
||||
!.idea/icon.png
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
@@ -41,6 +42,7 @@ frontend/src/xpack
|
||||
backend/xpack
|
||||
backend/router/entry_xpack.go
|
||||
backend/server/init_xpack.go
|
||||
backend/utils/xpack/xpack_xpack.go
|
||||
|
||||
.history/
|
||||
dist/
|
||||
@@ -50,3 +52,4 @@ install.sh
|
||||
quick_start.sh
|
||||
cmd/server/fileList.txt
|
||||
.fileList.txt
|
||||
1Panel.code-workspace
|
||||
|
||||
@@ -13,6 +13,7 @@ builds:
|
||||
- main: ./cmd/server/main.go
|
||||
binary: 1panel
|
||||
flags:
|
||||
- -tags=xpack
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s
|
||||
|
||||
Generated
BIN
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
+2
-2
@@ -5,7 +5,7 @@ PR are always welcome, even if they only contain small fixes like typos or a few
|
||||
|
||||
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
|
||||
|
||||
This [development guideline](https://kubeoperator.io/docs/dev/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
|
||||
This [development guideline](https://1panel.cn/docs/dev_manual/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
|
||||
|
||||
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
|
||||
|
||||
@@ -22,4 +22,4 @@ When reporting issues, always include:
|
||||
* Snapshots or log files if needed
|
||||
|
||||
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
||||
replace those parts with "REDACTED" or other strings like "****".
|
||||
replace those parts with "REDACTED" or other strings like "****".
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供防火墙和日志审计等功能;
|
||||
- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。
|
||||
|
||||
1Panel 三分钟速览:https://www.bilibili.com/video/BV1Mt421n7LZ/
|
||||
|
||||
## UI 展示
|
||||
|
||||

|
||||
@@ -50,13 +52,19 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
|
||||
|
||||
如果您在使用过程中发现任何安全问题,请通过以下方式直接联系我们:
|
||||
|
||||
- 邮箱:support@fit2cloud.com
|
||||
- 电话:400-052-0755
|
||||
- 邮箱:wanghe@fit2cloud.com
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#1Panel-dev/1Panel&Date)
|
||||
|
||||
## 我们的其他明星项目
|
||||
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
|
||||
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
|
||||
- [MeterSphere](https://github.com/metersphere/metersphere/) - 一站式开源自动化测试平台
|
||||
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
|
||||
|
||||
|
||||
## FOSSA Status
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_large)
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
如果您发现安全问题,请直接联系我们:
|
||||
|
||||
- wanghe@fit2cloud.com
|
||||
- zhengkun@fit2cloud.com
|
||||
- support@fit2cloud.com
|
||||
- 400-052-0755
|
||||
|
||||
感谢您的支持!
|
||||
|
||||
@@ -14,8 +11,5 @@
|
||||
All security bugs should be reported to the contact as below:
|
||||
|
||||
- wanghe@fit2cloud.com
|
||||
- zhengkun@fit2cloud.com
|
||||
- support@fit2cloud.com
|
||||
- +86 400-052-0755
|
||||
|
||||
Thanks for your support!
|
||||
|
||||
@@ -137,7 +137,7 @@ func (b *BaseApi) GetAppDetailByID(c *gin.Context) {
|
||||
// @Accept json
|
||||
// @Success 200 {object} response.IgnoredApp
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/ingored [get]
|
||||
// @Router /apps/ignored [get]
|
||||
func (b *BaseApi) GetIgnoredApp(c *gin.Context) {
|
||||
res, err := appService.GetIgnoredApp()
|
||||
if err != nil {
|
||||
|
||||
@@ -121,6 +121,15 @@ func (b *BaseApi) CheckIsSafety(c *gin.Context) {
|
||||
helper.SuccessWithData(c, status)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetResponsePage(c *gin.Context) {
|
||||
pageCode, err := authService.GetResponsePage()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, pageCode)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary Check System isDemo
|
||||
// @Description 判断是否为demo环境
|
||||
|
||||
@@ -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 获取首页基础数据
|
||||
|
||||
@@ -301,28 +301,83 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
files := form.File["file"]
|
||||
uploadFiles := form.File["file"]
|
||||
paths := form.Value["path"]
|
||||
|
||||
overwrite := true
|
||||
if ow, ok := form.Value["overwrite"]; ok {
|
||||
if len(ow) != 0 {
|
||||
parseBool, _ := strconv.ParseBool(ow[0])
|
||||
overwrite = parseBool
|
||||
}
|
||||
}
|
||||
|
||||
if len(paths) == 0 || !strings.Contains(paths[0], "/") {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error paths in request"))
|
||||
return
|
||||
}
|
||||
dir := path.Dir(paths[0])
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
|
||||
info, err := os.Stat(dir)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
mode, err := files.GetParentMode(dir)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if err = os.MkdirAll(dir, mode); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("mkdir %s failed, err: %v", dir, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
info, err = os.Stat(dir)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
mode := info.Mode()
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
|
||||
success := 0
|
||||
failures := make(buserr.MultiErr)
|
||||
for _, file := range files {
|
||||
if err := c.SaveUploadedFile(file, path.Join(paths[0], file.Filename)); err != nil {
|
||||
for _, file := range uploadFiles {
|
||||
dstFilename := path.Join(paths[0], file.Filename)
|
||||
dstDir := path.Dir(dstFilename)
|
||||
if !fileOp.Stat(dstDir) {
|
||||
if err = fileOp.CreateDir(dstDir, mode); err != nil {
|
||||
e := fmt.Errorf("create dir [%s] failed, err: %v", path.Dir(dstFilename), err)
|
||||
failures[file.Filename] = e
|
||||
global.LOG.Error(e)
|
||||
continue
|
||||
}
|
||||
}
|
||||
tmpFilename := dstFilename + ".tmp"
|
||||
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
|
||||
_ = os.Remove(tmpFilename)
|
||||
e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err)
|
||||
failures[file.Filename] = e
|
||||
global.LOG.Error(e)
|
||||
continue
|
||||
}
|
||||
dstInfo, statErr := os.Stat(dstFilename)
|
||||
if overwrite {
|
||||
_ = os.Remove(dstFilename)
|
||||
}
|
||||
|
||||
err = os.Rename(tmpFilename, dstFilename)
|
||||
if err != nil {
|
||||
_ = os.Remove(tmpFilename)
|
||||
e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err)
|
||||
failures[file.Filename] = e
|
||||
global.LOG.Error(e)
|
||||
continue
|
||||
}
|
||||
if statErr == nil {
|
||||
_ = os.Chmod(dstFilename, dstInfo.Mode())
|
||||
} else {
|
||||
_ = os.Chmod(dstFilename, mode)
|
||||
}
|
||||
success++
|
||||
}
|
||||
if success == 0 {
|
||||
@@ -539,16 +594,20 @@ func (b *BaseApi) Size(c *gin.Context) {
|
||||
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
|
||||
op := files.NewFileOp()
|
||||
dstDir = strings.TrimSpace(dstDir)
|
||||
mode, _ := files.GetParentMode(dstDir)
|
||||
if mode == 0 {
|
||||
mode = 0755
|
||||
}
|
||||
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
|
||||
if err = op.CreateDir(dstDir, os.ModePerm); err != nil {
|
||||
if err = op.CreateDir(dstDir, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
|
||||
|
||||
targetFile, err := os.OpenFile(filepath.Join(dstDir, fileName), os.O_RDWR|os.O_CREATE, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetFile.Close()
|
||||
|
||||
for i := 0; i < chunkCount; i++ {
|
||||
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i))
|
||||
|
||||
@@ -15,6 +15,13 @@ import (
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Load monitor datas
|
||||
// @Description 获取监控数据
|
||||
// @Param request body dto.MonitorSearch true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /hosts/monitor/search [post]
|
||||
func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
var req dto.MonitorSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -28,7 +35,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
var backdatas []dto.MonitorData
|
||||
if req.Param == "all" || req.Param == "cpu" || req.Param == "memory" || req.Param == "load" {
|
||||
var bases []model.MonitorBase
|
||||
if err := global.DB.
|
||||
if err := global.MonitorDB.
|
||||
Where("created_at > ? AND created_at < ?", req.StartTime, req.EndTime).
|
||||
Find(&bases).Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
@@ -45,7 +52,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
}
|
||||
if req.Param == "all" || req.Param == "io" {
|
||||
var bases []model.MonitorIO
|
||||
if err := global.DB.
|
||||
if err := global.MonitorDB.
|
||||
Where("created_at > ? AND created_at < ?", req.StartTime, req.EndTime).
|
||||
Find(&bases).Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
@@ -62,7 +69,7 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
}
|
||||
if req.Param == "all" || req.Param == "network" {
|
||||
var bases []model.MonitorNetwork
|
||||
if err := global.DB.
|
||||
if err := global.MonitorDB.
|
||||
Where("name = ? AND created_at > ? AND created_at < ?", req.Info, req.StartTime, req.EndTime).
|
||||
Find(&bases).Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
@@ -80,6 +87,30 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
|
||||
helper.SuccessWithData(c, backdatas)
|
||||
}
|
||||
|
||||
// @Tags Monitor
|
||||
// @Summary Clean monitor datas
|
||||
// @Description 清空监控数据
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /hosts/monitor/clean [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"}
|
||||
func (b *BaseApi) CleanMonitor(c *gin.Context) {
|
||||
if err := global.MonitorDB.Exec("DELETE FROM monitor_bases").Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if err := global.MonitorDB.Exec("DELETE FROM monitor_ios").Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if err := global.MonitorDB.Exec("DELETE FROM monitor_networks").Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
|
||||
netStat, _ := net.IOCounters(true)
|
||||
var options []string
|
||||
|
||||
@@ -203,3 +203,19 @@ func (b *BaseApi) OperateNodeModules(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Sync runtime status
|
||||
// @Description 同步运行环境状态
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes/sync [post]
|
||||
func (b *BaseApi) SyncStatus(c *gin.Context) {
|
||||
err := runtimeService.SyncRuntimeStatus()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
@@ -60,6 +60,28 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system setting
|
||||
// @Description 隐藏高级功能菜单
|
||||
// @Accept json
|
||||
// @Param request body dto.SettingUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/menu/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"隐藏高级功能菜单","formatEN":"Hide advanced feature menu."}
|
||||
func (b *BaseApi) UpdateMenu(c *gin.Context) {
|
||||
var req dto.SettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update(req.Key, req.Value); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system password
|
||||
// @Description 更新系统登录密码
|
||||
@@ -227,30 +249,6 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
||||
helper.SuccessWithData(c, global.CONF.System.DataDir)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Clean monitor datas
|
||||
// @Description 清空监控数据
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/monitor/clean [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"}
|
||||
func (b *BaseApi) CleanMonitor(c *gin.Context) {
|
||||
if err := global.DB.Exec("DELETE FROM monitor_bases").Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if err := global.DB.Exec("DELETE FROM monitor_ios").Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
if err := global.DB.Exec("DELETE FROM monitor_networks").Error; err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load mfa info
|
||||
// @Description 获取 mfa 信息
|
||||
|
||||
@@ -102,7 +102,7 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(redisConf.ContainerName, commands, pidMap)
|
||||
defer killBash(redisConf.ContainerName, pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
@@ -170,7 +170,7 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, command, pidMap)
|
||||
defer killBash(containerID, pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
|
||||
@@ -221,26 +221,26 @@ func loadMapFromDockerTop(containerID string) map[string]string {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) != 2 {
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
pidMap[parts[0]] = parts[1]
|
||||
pidMap[parts[0]] = strings.Join(parts, " ")
|
||||
}
|
||||
return pidMap
|
||||
}
|
||||
|
||||
func killBash(containerID, comm string, pidMap map[string]string) {
|
||||
func killBash(containerID string, pidMap map[string]string) {
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
newPidMap := loadMapFromDockerTop(containerID)
|
||||
for pid, command := range newPidMap {
|
||||
for pid, newCmd := range newPidMap {
|
||||
isOld := false
|
||||
for pid2 := range pidMap {
|
||||
if pid == pid2 {
|
||||
for pid2, oldCmd := range pidMap {
|
||||
if pid == pid2 && oldCmd == newCmd {
|
||||
isOld = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isOld && command == comm {
|
||||
if !isOld {
|
||||
_, _ = cmd.Execf("%s kill -9 %s", sudo, pid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,69 +305,6 @@ func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Website WAF
|
||||
// @Summary Load websit waf conf
|
||||
// @Description 获取网站 waf 配置
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteWafReq true "request"
|
||||
// @Success 200 {object} response.WebsiteWafConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/waf/config [post]
|
||||
func (b *BaseApi) GetWebsiteWafConfig(c *gin.Context) {
|
||||
var req request.WebsiteWafReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := websiteService.GetWafConfig(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Website WAF
|
||||
// @Summary Update website waf conf
|
||||
// @Description 更新 网站 waf 配置
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteWafUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/waf/update [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置修改 [domain]","formatEN":"WAF conf update [domain]"}
|
||||
func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
|
||||
var req request.WebsiteWafUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.UpdateWafConfig(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Website WAF
|
||||
// @Summary Update website waf file
|
||||
// @Description 更新 网站 waf 配置文件
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteWafUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/waf/file/update [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置文件修改 [domain]","formatEN":"WAF conf file update [domain]"}
|
||||
func (b *BaseApi) UpdateWebsiteWafFile(c *gin.Context) {
|
||||
var req request.WebsiteWafFileUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.UpdateWafFile(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Website Nginx
|
||||
// @Summary Update website nginx conf
|
||||
// @Description 更新 网站 nginx 配置
|
||||
@@ -433,7 +370,7 @@ func (b *BaseApi) ChangeDefaultServer(c *gin.Context) {
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Load websit php conf
|
||||
// @Summary Load website php conf
|
||||
// @Description 获取网站 php 配置
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
|
||||
@@ -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 {
|
||||
@@ -48,6 +48,8 @@ type ContainerOperate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Image string `json:"image" validate:"required"`
|
||||
Network string `json:"network"`
|
||||
Ipv4 string `json:"ipv4"`
|
||||
Ipv6 string `json:"ipv6"`
|
||||
PublishAllPorts bool `json:"publishAllPorts"`
|
||||
ExposedPorts []PortHelper `json:"exposedPorts"`
|
||||
Tty bool `json:"tty"`
|
||||
@@ -98,6 +100,7 @@ type ContainerStats struct {
|
||||
}
|
||||
|
||||
type VolumeHelper struct {
|
||||
Type string `json:"type"`
|
||||
SourceDir string `json:"sourceDir"`
|
||||
ContainerDir string `json:"containerDir"`
|
||||
Mode string `json:"mode"`
|
||||
|
||||
@@ -8,6 +8,7 @@ type CronjobCreate struct {
|
||||
Spec string `json:"spec" validate:"required"`
|
||||
|
||||
Script string `json:"script"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
@@ -28,6 +29,7 @@ type CronjobUpdate struct {
|
||||
Spec string `json:"spec" validate:"required"`
|
||||
|
||||
Script string `json:"script"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
@@ -70,6 +72,7 @@ type CronjobInfo struct {
|
||||
Spec string `json:"spec"`
|
||||
|
||||
Script string `json:"script"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
AppID string `json:"appID"`
|
||||
Website string `json:"website"`
|
||||
|
||||
@@ -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"`
|
||||
@@ -61,6 +69,8 @@ type DashboardCurrent struct {
|
||||
NetBytesSent uint64 `json:"netBytesSent"`
|
||||
NetBytesRecv uint64 `json:"netBytesRecv"`
|
||||
|
||||
GPUData []GPUInfo `json:"gpuData"`
|
||||
|
||||
ShotTime time.Time `json:"shotTime"`
|
||||
}
|
||||
|
||||
@@ -78,3 +88,18 @@ type DiskInfo struct {
|
||||
InodesFree uint64 `json:"inodesFree"`
|
||||
InodesUsedPercent float64 `json:"inodesUsedPercent"`
|
||||
}
|
||||
|
||||
type GPUInfo struct {
|
||||
Index uint `json:"index"`
|
||||
ProductName string `json:"productName"`
|
||||
GPUUtil string `json:"gpuUtil"`
|
||||
Temperature string `json:"temperature"`
|
||||
PerformanceState string `json:"performanceState"`
|
||||
PowerUsage string `json:"powerUsage"`
|
||||
PowerDraw string `json:"powerDraw"`
|
||||
MaxPowerLimit string `json:"maxPowerLimit"`
|
||||
MemoryUsage string `json:"memoryUsage"`
|
||||
MemUsed string `json:"memUsed"`
|
||||
MemTotal string `json:"memTotal"`
|
||||
FanSpeed string `json:"fanSpeed"`
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ type MysqlDBInfo struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission"`
|
||||
BackupCount int `json:"backupCount"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ type MysqlStatus struct {
|
||||
}
|
||||
|
||||
type MysqlVariables struct {
|
||||
BinlogCachSize string `json:"binlog_cache_size"`
|
||||
BinlogCacheSize string `json:"binlog_cache_size"`
|
||||
InnodbBufferPoolSize string `json:"innodb_buffer_pool_size"`
|
||||
InnodbLogBufferSize string `json:"innodb_log_buffer_size"`
|
||||
JoinBufferSize string `json:"join_buffer_size"`
|
||||
|
||||
@@ -20,7 +20,7 @@ type PostgresqlDBInfo struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SuperUser bool `json:"superUser"`
|
||||
BackupCount int `json:"backupCount"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ type FileCreate struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content"`
|
||||
IsDir bool `json:"isDir"`
|
||||
Mode int64 `json:"mode" validate:"required"`
|
||||
Mode int64 `json:"mode"`
|
||||
IsLink bool `json:"isLink"`
|
||||
IsSymlink bool `json:"isSymlink"`
|
||||
LinkPath string `json:"linkPath"`
|
||||
|
||||
@@ -69,24 +69,12 @@ type WebsiteOp struct {
|
||||
Operate string `json:"operate"`
|
||||
}
|
||||
|
||||
type WebsiteWafReq struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Rule string `json:"rule" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteRedirectUpdate struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Enable bool `json:"enable"`
|
||||
}
|
||||
|
||||
type WebsiteWafUpdate struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Enable bool `json:"enable"`
|
||||
}
|
||||
|
||||
type WebsiteRecover struct {
|
||||
WebsiteName string `json:"websiteName" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
@@ -207,12 +195,12 @@ type WebsiteRedirectReq struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteWafFileUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=cc ip_white ip_block url_white url_block cookie_block args_check post_check ua_check file_ext_block user_agent"`
|
||||
}
|
||||
|
||||
type WebsiteCommonReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type WafWebsite struct {
|
||||
Key string `json:"key"`
|
||||
Domains []string `json:"domains"`
|
||||
Host []string `json:"host"`
|
||||
}
|
||||
|
||||
@@ -31,11 +31,6 @@ type WebsiteNginxConfig struct {
|
||||
Params []NginxParam `json:"params"`
|
||||
}
|
||||
|
||||
type WebsiteWafConfig struct {
|
||||
Enable bool `json:"enable"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type WebsiteHTTPS struct {
|
||||
Enable bool `json:"enable"`
|
||||
HttpConfig string `json:"httpConfig"`
|
||||
|
||||
@@ -19,6 +19,7 @@ type SettingInfo struct {
|
||||
BindAddress string `json:"bindAddress"`
|
||||
PanelName string `json:"panelName"`
|
||||
Theme string `json:"theme"`
|
||||
MenuTabs string `json:"menuTabs"`
|
||||
Language string `json:"language"`
|
||||
DefaultNetwork string `json:"defaultNetwork"`
|
||||
LastCleanTime string `json:"lastCleanTime"`
|
||||
@@ -52,6 +53,10 @@ type SettingInfo struct {
|
||||
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
|
||||
|
||||
FileRecycleBin string `json:"fileRecycleBin"`
|
||||
|
||||
SnapshotIgnore string `json:"snapshotIgnore"`
|
||||
XpackHideMenu string `json:"xpackHideMenu"`
|
||||
NoAuthSetting string `json:"noAuthSetting"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
|
||||
@@ -9,6 +9,7 @@ type Cronjob struct {
|
||||
Type string `gorm:"type:varchar(64);not null" json:"type"`
|
||||
Spec string `gorm:"type:varchar(64);not null" json:"spec"`
|
||||
|
||||
Command string `gorm:"type:varchar(64)" json:"command"`
|
||||
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
|
||||
Script string `gorm:"longtext" json:"script"`
|
||||
Website string `gorm:"type:varchar(64)" json:"website"`
|
||||
|
||||
@@ -9,5 +9,6 @@ type DatabaseMysql struct {
|
||||
Username string `json:"username" gorm:"type:varchar(256);not null"`
|
||||
Password string `json:"password" gorm:"type:varchar(256);not null"`
|
||||
Permission string `json:"permission" gorm:"type:varchar(256);not null"`
|
||||
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
|
||||
Description string `json:"description" gorm:"type:varchar(256);"`
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ type DatabasePostgresql struct {
|
||||
Format string `json:"format" gorm:"type:varchar(64);not null"`
|
||||
Username string `json:"username" gorm:"type:varchar(256);not null"`
|
||||
Password string `json:"password" gorm:"type:varchar(256);not null"`
|
||||
SuperUser bool `json:"superUser" gorm:"type:varchar(64)" `
|
||||
SuperUser bool `json:"superUser" gorm:"type:varchar(64)"`
|
||||
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
|
||||
Description string `json:"description" gorm:"type:varchar(256);"`
|
||||
}
|
||||
|
||||
@@ -68,20 +68,20 @@ func (u *SettingRepo) Update(key, value string) error {
|
||||
}
|
||||
|
||||
func (u *SettingRepo) CreateMonitorBase(model model.MonitorBase) error {
|
||||
return global.DB.Create(&model).Error
|
||||
return global.MonitorDB.Create(&model).Error
|
||||
}
|
||||
func (u *SettingRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
|
||||
return global.DB.CreateInBatches(ioList, len(ioList)).Error
|
||||
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
|
||||
}
|
||||
func (u *SettingRepo) BatchCreateMonitorNet(ioList []model.MonitorNetwork) error {
|
||||
return global.DB.CreateInBatches(ioList, len(ioList)).Error
|
||||
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
|
||||
}
|
||||
func (u *SettingRepo) DelMonitorBase(timeForDelete time.Time) error {
|
||||
return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error
|
||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error
|
||||
}
|
||||
func (u *SettingRepo) DelMonitorIO(timeForDelete time.Time) error {
|
||||
return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error
|
||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error
|
||||
}
|
||||
func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
|
||||
return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
|
||||
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type IWebsiteRepo interface {
|
||||
WithDefaultServer() DBOption
|
||||
WithDomainLike(domain string) DBOption
|
||||
WithRuntimeID(runtimeID uint) DBOption
|
||||
WithIDs(ids []uint) DBOption
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
||||
List(opts ...DBOption) ([]model.Website, error)
|
||||
GetFirst(opts ...DBOption) (model.Website, error)
|
||||
@@ -36,9 +37,15 @@ func NewIWebsiteRepo() IWebsiteRepo {
|
||||
type WebsiteRepo struct {
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithAppInstallId(appInstallId uint) DBOption {
|
||||
func (w *WebsiteRepo) WithAppInstallId(appInstallID uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("app_install_id = ?", appInstallId)
|
||||
return db.Where("app_install_id = ?", appInstallID)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsiteRepo) WithIDs(ids []uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("id in (?)", ids)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ type IWebsiteDomainRepo interface {
|
||||
WithWebsiteId(websiteId uint) DBOption
|
||||
WithPort(port int) DBOption
|
||||
WithDomain(domain string) DBOption
|
||||
WithDomainLike(domain string) DBOption
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error)
|
||||
GetFirst(opts ...DBOption) (model.WebsiteDomain, error)
|
||||
GetBy(opts ...DBOption) ([]model.WebsiteDomain, error)
|
||||
@@ -44,6 +45,11 @@ func (w WebsiteDomainRepo) WithDomain(domain string) DBOption {
|
||||
return db.Where("domain = ?", domain)
|
||||
}
|
||||
}
|
||||
func (w WebsiteDomainRepo) WithDomainLike(domain string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("domain like ?", "%"+domain+"%")
|
||||
}
|
||||
}
|
||||
func (w WebsiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) {
|
||||
var domains []model.WebsiteDomain
|
||||
db := getDb(opts...).Model(&model.WebsiteDomain{})
|
||||
|
||||
@@ -527,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
|
||||
}
|
||||
|
||||
@@ -749,8 +749,10 @@ func syncAppInstallStatus(appInstall *model.AppInstall) error {
|
||||
|
||||
switch {
|
||||
case count == 0:
|
||||
appInstall.Status = constant.Error
|
||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||
if appInstall.Status != constant.Error {
|
||||
appInstall.Status = constant.SyncErr
|
||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||
}
|
||||
case exitedCount == total:
|
||||
appInstall.Status = constant.Stopped
|
||||
case runningCount == total:
|
||||
@@ -776,7 +778,7 @@ func syncAppInstallStatus(appInstall *model.AppInstall) error {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -37,7 +39,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
composeV2 "github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -364,6 +365,7 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
|
||||
}
|
||||
_ = websiteRepo.DeleteAll(ctx)
|
||||
_ = websiteDomainRepo.DeleteAll(ctx)
|
||||
xpack.RemoveTamper("")
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
|
||||
case constant.AppPostgresql:
|
||||
@@ -693,8 +695,8 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
|
||||
strArray[i] = strings.ToLower(fmt.Sprintf("%v", t[i]))
|
||||
}
|
||||
envParams[k] = strings.Join(strArray, ",")
|
||||
default:
|
||||
envParams[k] = t.(string)
|
||||
case map[string]interface{}:
|
||||
handleMap(t, envParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -827,7 +829,7 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var options dockerTypes.ContainerListOptions
|
||||
var options container.ListOptions
|
||||
list, err := client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -849,38 +851,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
|
||||
@@ -996,6 +994,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
|
||||
}
|
||||
@@ -1192,14 +1227,20 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
|
||||
return buserr.New(constant.ErrFileParse)
|
||||
}
|
||||
serviceValue := service.(map[string]interface{})
|
||||
deploy := map[string]interface{}{
|
||||
"resources": map[string]interface{}{
|
||||
"limits": map[string]interface{}{
|
||||
"cpus": "${CPUS}",
|
||||
"memory": "${MEMORY_LIMIT}",
|
||||
},
|
||||
},
|
||||
|
||||
deploy := map[string]interface{}{}
|
||||
if de, ok := serviceValue["deploy"]; ok {
|
||||
deploy = de.(map[string]interface{})
|
||||
}
|
||||
resource := map[string]interface{}{}
|
||||
if res, ok := deploy["resources"]; ok {
|
||||
resource = res.(map[string]interface{})
|
||||
}
|
||||
resource["limits"] = map[string]interface{}{
|
||||
"cpus": "${CPUS}",
|
||||
"memory": "${MEMORY_LIMIT}",
|
||||
}
|
||||
deploy["resources"] = resource
|
||||
serviceValue["deploy"] = deploy
|
||||
|
||||
ports, ok := serviceValue["ports"].([]interface{})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -19,6 +20,7 @@ type AuthService struct{}
|
||||
|
||||
type IAuthService interface {
|
||||
CheckIsSafety(code string) (string, error)
|
||||
GetResponsePage() (string, error)
|
||||
VerifyCode(code string) (bool, error)
|
||||
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error)
|
||||
LogOut(c *gin.Context) error
|
||||
@@ -42,7 +44,7 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d
|
||||
if err != nil {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
if info.Password != pass || nameSetting.Value != info.Name {
|
||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
@@ -78,7 +80,7 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.Password != pass || nameSetting.Value != info.Name {
|
||||
if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name {
|
||||
return nil, constant.ErrAuth
|
||||
}
|
||||
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
|
||||
@@ -168,7 +170,7 @@ func (u *AuthService) VerifyCode(code string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return setting.Value == code, nil
|
||||
return hmac.Equal([]byte(setting.Value), []byte(code)), nil
|
||||
}
|
||||
|
||||
func (u *AuthService) CheckIsSafety(code string) (string, error) {
|
||||
@@ -184,3 +186,11 @@ func (u *AuthService) CheckIsSafety(code string) (string, error) {
|
||||
}
|
||||
return "unpass", nil
|
||||
}
|
||||
|
||||
func (u *AuthService) GetResponsePage() (string, error) {
|
||||
pageCode, err := settingRepo.Get(settingRepo.WithByKey("NoAuthSetting"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return pageCode.Value, nil
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/client"
|
||||
@@ -86,7 +88,9 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
options := types.ContainerListOptions{All: true}
|
||||
options := container.ListOptions{
|
||||
All: true,
|
||||
}
|
||||
if len(req.Filters) != 0 {
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", req.Filters)
|
||||
@@ -215,7 +219,7 @@ func (u *ContainerService) List() ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -236,7 +240,7 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||
list, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -336,7 +340,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
||||
|
||||
data := dto.ResourceLimit{
|
||||
CPU: cpuCounts,
|
||||
Memory: int(memoryInfo.Total),
|
||||
Memory: memoryInfo.Total,
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
@@ -375,14 +379,14 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name)
|
||||
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
|
||||
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
|
||||
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
||||
return fmt.Errorf("create successful but start failed, err: %v", err)
|
||||
}
|
||||
return nil
|
||||
@@ -409,6 +413,18 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
networkSettings := oldContainer.NetworkSettings
|
||||
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
||||
if bridgeNetworkSettings.IPAMConfig != nil {
|
||||
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address
|
||||
data.Ipv4 = ipv4Address
|
||||
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address
|
||||
data.Ipv6 = ipv6Address
|
||||
} else {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAddress
|
||||
}
|
||||
|
||||
data.Cmd = oldContainer.Config.Cmd
|
||||
data.OpenStdin = oldContainer.Config.OpenStdin
|
||||
data.Tty = oldContainer.Config.Tty
|
||||
@@ -441,7 +457,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
if oldContainer.HostConfig.Memory != 0 {
|
||||
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
|
||||
}
|
||||
data.Volumes = loadVolumeBinds(oldContainer.HostConfig.Binds)
|
||||
data.Volumes = loadVolumeBinds(oldContainer.Mounts)
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
@@ -470,7 +486,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||
if err := client.ContainerRemove(ctx, req.ContainerID, container.RemoveOptions{Force: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -481,13 +497,13 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
}
|
||||
|
||||
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
||||
return fmt.Errorf("update container failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
|
||||
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
|
||||
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
|
||||
return fmt.Errorf("update successful but start failed, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -522,18 +538,18 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||
if err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
|
||||
con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
||||
return fmt.Errorf("upgrade container failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Infof("upgrade container %s successful! now check if the container is started.", req.Name)
|
||||
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
|
||||
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
|
||||
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -565,7 +581,7 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
|
||||
global.LOG.Infof("start container %s operation %s", item, req.Operation)
|
||||
switch req.Operation {
|
||||
case constant.ContainerOpStart:
|
||||
err = client.ContainerStart(ctx, item, types.ContainerStartOptions{})
|
||||
err = client.ContainerStart(ctx, item, container.StartOptions{})
|
||||
case constant.ContainerOpStop:
|
||||
err = client.ContainerStop(ctx, item, container.StopOptions{})
|
||||
case constant.ContainerOpRestart:
|
||||
@@ -577,7 +593,7 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
|
||||
case constant.ContainerOpUnpause:
|
||||
err = client.ContainerUnpause(ctx, item)
|
||||
case constant.ContainerOpRemove:
|
||||
err = client.ContainerRemove(ctx, item, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||
err = client.ContainerRemove(ctx, item, container.RemoveOptions{RemoveVolumes: true, Force: true})
|
||||
}
|
||||
}
|
||||
return err
|
||||
@@ -611,7 +627,7 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
|
||||
if err := client.ContainerStart(ctx, containerItem.ID, types.ContainerStartOptions{}); err != nil {
|
||||
if err := client.ContainerStart(ctx, containerItem.ID, container.StartOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -622,22 +638,26 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
|
||||
if cmd.CheckIllegal(container, since, tail) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
command := fmt.Sprintf("docker logs %s", container)
|
||||
commandName := "docker"
|
||||
commandArg := []string{"logs", container}
|
||||
if containerType == "compose" {
|
||||
command = fmt.Sprintf("docker-compose -f %s logs", container)
|
||||
commandName = "docker-compose"
|
||||
commandArg = []string{"-f", container, "logs"}
|
||||
}
|
||||
if tail != "0" {
|
||||
command += " --tail " + tail
|
||||
commandArg = append(commandArg, "--tail")
|
||||
commandArg = append(commandArg, tail)
|
||||
}
|
||||
if since != "all" {
|
||||
command += " --since " + since
|
||||
commandArg = append(commandArg, "--since")
|
||||
commandArg = append(commandArg, since)
|
||||
}
|
||||
if follow {
|
||||
command += " -f"
|
||||
commandArg = append(commandArg, "-f")
|
||||
}
|
||||
command += " 2>&1"
|
||||
cmd := exec.Command("bash", "-c", command)
|
||||
if !follow {
|
||||
cmd := exec.Command(commandName, commandArg...)
|
||||
cmd.Stderr = cmd.Stdout
|
||||
stdout, _ := cmd.CombinedOutput()
|
||||
if !utf8.Valid(stdout) {
|
||||
return errors.New("invalid utf8")
|
||||
@@ -648,51 +668,53 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := exec.Command(commandName, commandArg...)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
||||
_ = cmd.Wait()
|
||||
}()
|
||||
var exitCh chan struct{}
|
||||
if follow {
|
||||
go func() {
|
||||
_, wsData, _ := wsConn.ReadMessage()
|
||||
if string(wsData) == "close conn" {
|
||||
exitCh <- struct{}{}
|
||||
}
|
||||
}()
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
if err := cmd.Start(); err != nil {
|
||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
||||
return err
|
||||
}
|
||||
exitCh := make(chan struct{})
|
||||
go func() {
|
||||
_, wsData, _ := wsConn.ReadMessage()
|
||||
if string(wsData) == "close conn" {
|
||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
||||
exitCh <- struct{}{}
|
||||
}
|
||||
}()
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
select {
|
||||
case <-exitCh:
|
||||
return nil
|
||||
default:
|
||||
n, err := stdout.Read(buffer)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return err
|
||||
go func() {
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
select {
|
||||
case <-exitCh:
|
||||
return
|
||||
default:
|
||||
n, err := stdout.Read(buffer)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
global.LOG.Errorf("read bytes from log failed, err: %v", err)
|
||||
continue
|
||||
}
|
||||
if !utf8.Valid(buffer[:n]) {
|
||||
continue
|
||||
}
|
||||
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
|
||||
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
|
||||
global.LOG.Errorf("send message with log to ws failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
_ = cmd.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) {
|
||||
@@ -723,7 +745,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
|
||||
@@ -736,7 +757,7 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
options := types.ContainerListOptions{All: true}
|
||||
options := container.ListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name))
|
||||
containers, err := cli.ContainerList(context.Background(), options)
|
||||
@@ -791,7 +812,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 +916,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
|
||||
}
|
||||
@@ -976,8 +998,21 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
|
||||
case "host", "none", "bridge":
|
||||
hostConf.NetworkMode = container.NetworkMode(req.Network)
|
||||
}
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
|
||||
if req.Ipv4 != "" || req.Ipv6 != "" {
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{
|
||||
req.Network: {
|
||||
IPAMConfig: &network.EndpointIPAMConfig{
|
||||
IPv4Address: req.Ipv4,
|
||||
IPv6Address: req.Ipv6,
|
||||
},
|
||||
}}
|
||||
} else {
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
|
||||
}
|
||||
} else {
|
||||
if req.Ipv4 != "" || req.Ipv6 != "" {
|
||||
return nil, nil, nil, fmt.Errorf("Please set up the network")
|
||||
}
|
||||
networkConf = network.NetworkingConfig{}
|
||||
}
|
||||
|
||||
@@ -994,10 +1029,19 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
|
||||
hostConf.MemorySwap = 0
|
||||
hostConf.PortBindings = portMap
|
||||
hostConf.Binds = []string{}
|
||||
hostConf.Mounts = []mount.Mount{}
|
||||
config.Volumes = make(map[string]struct{})
|
||||
for _, volume := range req.Volumes {
|
||||
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
|
||||
if volume.Type == "volume" {
|
||||
hostConf.Mounts = append(hostConf.Mounts, mount.Mount{
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: volume.SourceDir,
|
||||
Target: volume.ContainerDir,
|
||||
})
|
||||
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||
} else {
|
||||
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
|
||||
}
|
||||
}
|
||||
return &config, &hostConf, &networkConf, nil
|
||||
}
|
||||
@@ -1018,46 +1062,27 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
|
||||
global.LOG.Errorf("recreate after container update failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := client.ContainerStart(ctx, oldContainer.ID, types.ContainerStartOptions{}); err != nil {
|
||||
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil {
|
||||
global.LOG.Errorf("restart after container update failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Errorf("recreate after container update successful")
|
||||
}
|
||||
|
||||
func loadVolumeBinds(binds []string) []dto.VolumeHelper {
|
||||
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
|
||||
var datas []dto.VolumeHelper
|
||||
for _, bind := range binds {
|
||||
parts := strings.Split(bind, ":")
|
||||
var volumeItem dto.VolumeHelper
|
||||
if len(parts) > 3 {
|
||||
continue
|
||||
volumeItem.Type = string(bind.Type)
|
||||
if bind.Type == "volume" {
|
||||
volumeItem.SourceDir = bind.Name
|
||||
} else {
|
||||
volumeItem.SourceDir = bind.Source
|
||||
}
|
||||
volumeItem.SourceDir = parts[0]
|
||||
if len(parts) == 1 {
|
||||
volumeItem.ContainerDir = parts[0]
|
||||
volumeItem.ContainerDir = bind.Destination
|
||||
volumeItem.Mode = "ro"
|
||||
if bind.RW {
|
||||
volumeItem.Mode = "rw"
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
switch parts[1] {
|
||||
case "r", "ro":
|
||||
volumeItem.ContainerDir = parts[0]
|
||||
volumeItem.Mode = "ro"
|
||||
case "rw":
|
||||
volumeItem.ContainerDir = parts[0]
|
||||
volumeItem.Mode = "rw"
|
||||
default:
|
||||
volumeItem.ContainerDir = parts[1]
|
||||
volumeItem.Mode = "rw"
|
||||
}
|
||||
}
|
||||
if len(parts) == 3 {
|
||||
volumeItem.ContainerDir = parts[1]
|
||||
if parts[2] == "r" {
|
||||
volumeItem.Mode = "ro"
|
||||
} else {
|
||||
volumeItem.Mode = parts[2]
|
||||
}
|
||||
}
|
||||
datas = append(datas, volumeItem)
|
||||
}
|
||||
return datas
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -20,7 +21,6 @@ import (
|
||||
"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/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -40,7 +40,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
options := types.ContainerListOptions{All: true}
|
||||
options := container.ListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", composeProjectLabel)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -266,6 +265,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
upMap["name"] = req.Name
|
||||
upMap["spec"] = spec
|
||||
upMap["script"] = req.Script
|
||||
upMap["command"] = req.Command
|
||||
upMap["container_name"] = req.ContainerName
|
||||
upMap["app_id"] = req.AppID
|
||||
upMap["website"] = req.Website
|
||||
|
||||
@@ -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 {
|
||||
@@ -326,7 +326,7 @@ func handleBackupLogs(targetDir, fileName string) error {
|
||||
if len(logFiles) != 0 {
|
||||
for i := 0; i < len(logFiles); i++ {
|
||||
if !logFiles[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(itemDir, logFiles[i].Name())}, dirItem)
|
||||
_ = common.CopyFile(path.Join(itemDir, logFiles[i].Name()), dirItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func handleBackupLogs(targetDir, fileName string) error {
|
||||
if len(logFiles2) != 0 {
|
||||
for i := 0; i < len(logFiles2); i++ {
|
||||
if !logFiles2[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(itemDir2, logFiles2[i].Name())}, dirItem)
|
||||
_ = common.CopyFile(path.Join(itemDir2, logFiles2[i].Name()), dirItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,7 +354,7 @@ func handleBackupLogs(targetDir, fileName string) error {
|
||||
if len(systemLogFiles) != 0 {
|
||||
for i := 0; i < len(systemLogFiles); i++ {
|
||||
if !systemLogFiles[i].IsDir() {
|
||||
_ = cpBinary([]string{path.Join(systemLogDir, systemLogFiles[i].Name())}, systemDir)
|
||||
_ = common.CopyFile(path.Join(systemLogDir, systemLogFiles[i].Name()), systemDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,7 +370,7 @@ func handleBackupLogs(targetDir, fileName string) error {
|
||||
if len(loginLogFiles) != 0 {
|
||||
for i := 0; i < len(loginLogFiles); i++ {
|
||||
if !loginLogFiles[i].IsDir() && (strings.HasPrefix(loginLogFiles[i].Name(), "secure") || strings.HasPrefix(loginLogFiles[i].Name(), "auth.log")) {
|
||||
_ = cpBinary([]string{path.Join("/var/log", loginLogFiles[i].Name())}, loginDir)
|
||||
_ = common.CopyFile(path.Join("/var/log", loginLogFiles[i].Name()), loginDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -38,7 +39,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
|
||||
script := cronjob.Script
|
||||
if len(cronjob.ContainerName) != 0 {
|
||||
script = fmt.Sprintf("docker exec %s %s", cronjob.ContainerName, cronjob.Script)
|
||||
command := "sh"
|
||||
if len(cronjob.Command) != 0 {
|
||||
command = cronjob.Command
|
||||
}
|
||||
script = fmt.Sprintf("docker exec %s %s -c \"%s\"", cronjob.ContainerName, command, strings.ReplaceAll(cronjob.Script, "\"", "\\\""))
|
||||
}
|
||||
err = u.handleShell(cronjob.Type, cronjob.Name, script, record.Records)
|
||||
u.removeExpiredLog(*cronjob)
|
||||
@@ -82,7 +87,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 {
|
||||
@@ -268,6 +276,21 @@ func loadClientMap(backupAccounts string) (map[string]cronjobUploadHelper, error
|
||||
if account.BackupPath != "/" {
|
||||
pathItem = strings.TrimPrefix(account.BackupPath, "/")
|
||||
}
|
||||
if target == constant.Local {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := varMap["dir"]; !ok {
|
||||
return nil, errors.New("load local backup dir failed")
|
||||
}
|
||||
baseDir, ok := varMap["dir"].(string)
|
||||
if ok {
|
||||
pathItem = baseDir
|
||||
} else {
|
||||
return nil, fmt.Errorf("error type dir: %T", varMap["dir"])
|
||||
}
|
||||
}
|
||||
clients[target] = cronjobUploadHelper{
|
||||
client: client,
|
||||
backupPath: pathItem,
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
@@ -22,6 +24,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 +52,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()
|
||||
@@ -132,6 +156,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
|
||||
if ioOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
@@ -183,7 +208,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")
|
||||
|
||||
@@ -264,3 +292,21 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
})
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadGPUInfo() []dto.GPUInfo {
|
||||
list := xpack.LoadGpuInfo()
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var data []dto.GPUInfo
|
||||
for _, gpu := range list {
|
||||
var dataItem dto.GPUInfo
|
||||
if err := copier.Copy(&dataItem, &gpu); err != nil {
|
||||
continue
|
||||
}
|
||||
dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit
|
||||
dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal
|
||||
data = append(data, dataItem)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -190,11 +190,13 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteList := databases
|
||||
for _, data := range datas {
|
||||
hasOld := false
|
||||
for _, oldData := range databases {
|
||||
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.MysqlName, data.MysqlName) {
|
||||
for i := 0; i < len(databases); i++ {
|
||||
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MysqlName, data.MysqlName) {
|
||||
hasOld = true
|
||||
deleteList = append(deleteList[:i], deleteList[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -208,6 +210,9 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, delItem := range deleteList {
|
||||
_ = mysqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -342,7 +347,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 +360,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
|
||||
|
||||
@@ -216,11 +216,13 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteList := databases
|
||||
for _, data := range datas {
|
||||
hasOld := false
|
||||
for _, oldData := range databases {
|
||||
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.PostgresqlName, data.PostgresqlName) {
|
||||
for i := 0; i < len(databases); i++ {
|
||||
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].PostgresqlName, data.PostgresqlName) {
|
||||
hasOld = true
|
||||
deleteList = append(deleteList[:i], deleteList[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -234,6 +236,9 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, delItem := range deleteList {
|
||||
_ = postgresqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -393,7 +398,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 +411,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
|
||||
}
|
||||
|
||||
|
||||
@@ -116,22 +116,32 @@ func (f *FileService) GetFileTree(op request.FileOption) ([]response.FileTree, e
|
||||
}
|
||||
|
||||
func (f *FileService) Create(op request.FileCreate) error {
|
||||
if files.IsInvalidChar(op.Path) {
|
||||
return buserr.New("ErrInvalidChar")
|
||||
}
|
||||
fo := files.NewFileOp()
|
||||
if fo.Stat(op.Path) {
|
||||
return buserr.New(constant.ErrFileIsExit)
|
||||
}
|
||||
if op.IsDir {
|
||||
return fo.CreateDir(op.Path, fs.FileMode(op.Mode))
|
||||
} else {
|
||||
if op.IsLink {
|
||||
if !fo.Stat(op.LinkPath) {
|
||||
return buserr.New(constant.ErrLinkPathNotFound)
|
||||
}
|
||||
return fo.LinkFile(op.LinkPath, op.Path, op.IsSymlink)
|
||||
mode := op.Mode
|
||||
if mode == 0 {
|
||||
fileInfo, err := os.Stat(filepath.Dir(op.Path))
|
||||
if err == nil {
|
||||
mode = int64(fileInfo.Mode().Perm())
|
||||
} else {
|
||||
return fo.CreateFile(op.Path)
|
||||
mode = 0755
|
||||
}
|
||||
}
|
||||
if op.IsDir {
|
||||
return fo.CreateDir(op.Path, fs.FileMode(mode))
|
||||
}
|
||||
if op.IsLink {
|
||||
if !fo.Stat(op.LinkPath) {
|
||||
return buserr.New(constant.ErrLinkPathNotFound)
|
||||
}
|
||||
return fo.LinkFile(op.LinkPath, op.Path, op.IsSymlink)
|
||||
}
|
||||
return fo.CreateFileWithMode(op.Path, fs.FileMode(mode))
|
||||
}
|
||||
|
||||
func (f *FileService) Delete(op request.FileDelete) error {
|
||||
@@ -236,6 +246,9 @@ func (f *FileService) SaveContent(edit request.FileEdit) error {
|
||||
}
|
||||
|
||||
func (f *FileService) ChangeName(req request.FileRename) error {
|
||||
if files.IsInvalidChar(req.NewName) {
|
||||
return buserr.New("ErrInvalidChar")
|
||||
}
|
||||
fo := files.NewFileOp()
|
||||
return fo.Rename(req.OldName, req.NewName)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
@@ -43,7 +45,7 @@ func NewIImageService() IImageService {
|
||||
}
|
||||
func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
var (
|
||||
list []types.ImageSummary
|
||||
list []image.Summary
|
||||
records []dto.ImageInfo
|
||||
backDatas []dto.ImageInfo
|
||||
)
|
||||
@@ -51,11 +53,11 @@ func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
list, err = client.ImageList(context.Background(), types.ImageListOptions{})
|
||||
list, err = client.ImageList(context.Background(), image.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
containers, _ := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||
containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
if len(req.Info) != 0 {
|
||||
length, count := len(list), 0
|
||||
for count < length {
|
||||
@@ -104,11 +106,11 @@ func (u *ImageService) ListAll() ([]dto.ImageInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := client.ImageList(context.Background(), types.ImageListOptions{})
|
||||
list, err := client.ImageList(context.Background(), image.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containers, _ := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||
containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
||||
for _, image := range list {
|
||||
size := formatFileSize(image.Size)
|
||||
records = append(records, dto.ImageInfo{
|
||||
@@ -124,14 +126,14 @@ func (u *ImageService) ListAll() ([]dto.ImageInfo, error) {
|
||||
|
||||
func (u *ImageService) List() ([]dto.Options, error) {
|
||||
var (
|
||||
list []types.ImageSummary
|
||||
list []image.Summary
|
||||
backDatas []dto.Options
|
||||
)
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err = client.ImageList(context.Background(), types.ImageListOptions{})
|
||||
list, err = client.ImageList(context.Background(), image.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -153,22 +153,20 @@ func (r RecycleBinService) Clear() error {
|
||||
}
|
||||
|
||||
func getClashDir(realPath string) (string, error) {
|
||||
trimmedPath := strings.Trim(realPath, "/")
|
||||
parts := strings.Split(trimmedPath, "/")
|
||||
dir := ""
|
||||
if len(parts) > 0 {
|
||||
dir = parts[0]
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == "/" {
|
||||
continue
|
||||
}
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == dir {
|
||||
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
if strings.HasPrefix(realPath, p.Mountpoint) {
|
||||
clashDir := path.Join(p.Mountpoint, ".1panel_clash")
|
||||
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return clashDir, nil
|
||||
}
|
||||
}
|
||||
return constant.RecycleBinDir, createClashDir(constant.RecycleBinDir)
|
||||
|
||||
@@ -42,6 +42,7 @@ type IRuntimeService interface {
|
||||
GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error)
|
||||
OperateNodeModules(req request.NodeModuleOperateReq) error
|
||||
SyncForRestart() error
|
||||
SyncRuntimeStatus() error
|
||||
}
|
||||
|
||||
func NewRuntimeService() IRuntimeService {
|
||||
@@ -586,3 +587,16 @@ func (r *RuntimeService) SyncForRestart() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RuntimeService) SyncRuntimeStatus() error {
|
||||
runtimes, err := runtimeRepo.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, runtime := range runtimes {
|
||||
if runtime.Type == constant.RuntimeNode {
|
||||
_ = SyncRuntimeContainerStatus(&runtime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -181,33 +181,20 @@ func SyncRuntimeContainerStatus(runtime *model.Runtime) error {
|
||||
}
|
||||
container := containers[0]
|
||||
|
||||
interval := 10 * time.Second
|
||||
retries := 60
|
||||
for i := 0; i < retries; i++ {
|
||||
resp, err := cli.InspectContainer(container.ID)
|
||||
if err != nil {
|
||||
time.Sleep(interval)
|
||||
continue
|
||||
switch container.State {
|
||||
case "exited":
|
||||
runtime.Status = constant.RuntimeError
|
||||
case "running":
|
||||
runtime.Status = constant.RuntimeRunning
|
||||
case "paused":
|
||||
runtime.Status = constant.RuntimeStopped
|
||||
default:
|
||||
if runtime.Status != constant.RuntimeBuildIng {
|
||||
runtime.Status = constant.RuntimeStopped
|
||||
}
|
||||
if resp.State.Health != nil {
|
||||
status := strings.ToLower(resp.State.Health.Status)
|
||||
switch status {
|
||||
case "starting":
|
||||
runtime.Status = constant.RuntimeStarting
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
case "healthy":
|
||||
runtime.Status = constant.RuntimeRunning
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
return nil
|
||||
case "unhealthy":
|
||||
runtime.Status = constant.RuntimeUnhealthy
|
||||
_ = runtimeRepo.Save(runtime)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
time.Sleep(interval)
|
||||
}
|
||||
return nil
|
||||
|
||||
return runtimeRepo.Save(runtime)
|
||||
}
|
||||
|
||||
func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
-670
@@ -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,152 +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, u.OriginalPath)
|
||||
|
||||
_, _ = 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 {
|
||||
_ = u.saveJson(snapJson, u.OriginalPath)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -319,133 +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()
|
||||
}()
|
||||
|
||||
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
||||
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", u.OriginalPath))
|
||||
if err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("read snapjson file failed, err: %v", 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
|
||||
}
|
||||
|
||||
@@ -477,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)
|
||||
|
||||
@@ -499,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
|
||||
@@ -548,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
|
||||
@@ -599,263 +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 "snapshot":
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
var status model.SnapshotStatus
|
||||
go snapAppData(snapHelper{Wg: &wg, Status: &status}, u.OriginalPath)
|
||||
wg.Wait()
|
||||
if status.AppData != constant.StatusDone {
|
||||
return errors.New(status.AppData)
|
||||
}
|
||||
case "recover":
|
||||
if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", u.OriginalPath); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(path.Join(source, "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(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "re-recover":
|
||||
if _, err := os.Stat(path.Join(source, "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(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "rollback":
|
||||
if _, err := os.Stat(path.Join(source, "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(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()
|
||||
@@ -878,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{}{
|
||||
@@ -917,62 +385,6 @@ func updateRollbackStatus(id uint, status string, message string) {
|
||||
}
|
||||
}
|
||||
|
||||
func cpBinary(src []string, dst string) error {
|
||||
global.LOG.Debugf(fmt.Sprintf("\\cp -f %s %s", strings.Join(src, " "), dst))
|
||||
stdout, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", strings.Join(src, " "), dst))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
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 {
|
||||
@@ -1025,64 +437,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) {
|
||||
@@ -1099,9 +479,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,15 @@ func snapPanel(snap snapHelper, targetDir string) {
|
||||
defer snap.Wg.Done()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
|
||||
status := constant.StatusDone
|
||||
if err := cpBinary([]string{"/usr/local/bin/1panel", "/usr/local/bin/1pctl", "/etc/systemd/system/1panel.service"}, targetDir); err != nil {
|
||||
if err := common.CopyFile("/usr/local/bin/1panel", path.Join(targetDir, "1panel")); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
|
||||
if err := common.CopyFile("/usr/local/bin/1pctl", targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
|
||||
if err := common.CopyFile("/etc/systemd/system/1panel.service", targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
snap.Status.Panel = status
|
||||
@@ -59,7 +67,7 @@ func snapDaemonJson(snap snapHelper, targetDir string) {
|
||||
return
|
||||
}
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"daemon_json": constant.Running})
|
||||
if err := cpBinary([]string{"/etc/docker/daemon.json"}, path.Join(targetDir, "daemon.json")); err != nil {
|
||||
if err := common.CopyFile("/etc/docker/daemon.json", targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
snap.Status.DaemonJson = status
|
||||
@@ -138,8 +146,17 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
||||
if strings.Contains(localDir, dataDir) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(localDir, dataDir, "") + ";")
|
||||
}
|
||||
|
||||
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
|
||||
rules := strings.Split(ignoreVal.Value, ",")
|
||||
for _, ignore := range rules {
|
||||
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
|
||||
continue
|
||||
}
|
||||
exclusionRules += ("." + strings.ReplaceAll(ignore, dataDir, "") + ";")
|
||||
}
|
||||
_ = 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()
|
||||
@@ -148,13 +165,10 @@ 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) {
|
||||
defer func() {
|
||||
global.LOG.Debugf("remove snapshot file %s", rootDir)
|
||||
_ = os.RemoveAll(rootDir)
|
||||
}()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusRunning})
|
||||
tmpDir := path.Join(global.CONF.System.TmpDir, "system")
|
||||
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
|
||||
@@ -175,15 +189,13 @@ func snapCompress(snap snapHelper, rootDir string) {
|
||||
snap.Status.Compress = constant.StatusDone
|
||||
snap.Status.Size = size
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusDone, "size": size})
|
||||
|
||||
global.LOG.Debugf("remove snapshot file %s", rootDir)
|
||||
_ = os.RemoveAll(rootDir)
|
||||
}
|
||||
|
||||
func snapUpload(snap snapHelper, accounts string, file string) {
|
||||
source := path.Join(global.CONF.System.TmpDir, "system", path.Base(file))
|
||||
defer func() {
|
||||
global.LOG.Debugf("remove snapshot file %s", source)
|
||||
_ = os.Remove(source)
|
||||
}()
|
||||
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusUploading})
|
||||
accountMap, err := loadClientMap(accounts)
|
||||
if err != nil {
|
||||
@@ -204,6 +216,9 @@ func snapUpload(snap snapHelper, accounts string, file string) {
|
||||
}
|
||||
snap.Status.Upload = constant.StatusDone
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusDone})
|
||||
|
||||
global.LOG.Debugf("remove snapshot file %s", source)
|
||||
_ = os.Remove(source)
|
||||
}
|
||||
|
||||
func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
@@ -213,6 +228,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
}
|
||||
}
|
||||
|
||||
exMap := make(map[string]struct{})
|
||||
exStr := ""
|
||||
excludes := strings.Split(exclusionRules, ";")
|
||||
excludes = append(excludes, "*.sock")
|
||||
@@ -220,8 +236,12 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
||||
if len(exclude) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := exMap[exclude]; ok {
|
||||
continue
|
||||
}
|
||||
exStr += " --exclude "
|
||||
exStr += exclude
|
||||
exMap[exclude] = struct{}{}
|
||||
}
|
||||
|
||||
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
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/common"
|
||||
"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"); 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"); 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"); 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(path.Join(src, "docker/daemon.json"))
|
||||
_, 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)
|
||||
}
|
||||
if err := common.CopyFile(src, dst); err != nil {
|
||||
return fmt.Errorf("cp file failed, err: %v", 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!")
|
||||
}
|
||||
@@ -177,7 +177,11 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
||||
},
|
||||
}
|
||||
if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil {
|
||||
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.OldValue, err)
|
||||
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
|
||||
}
|
||||
|
||||
if err = NewIHostService().Update(1, map[string]interface{}{"port": req.NewValue}); err != nil {
|
||||
global.LOG.Errorf("reset host port %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -44,7 +43,7 @@ func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
|
||||
global.LOG.Infof("load latest version failed, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if !common.CompareVersion(string(latestVersion), currentVersion.Value) {
|
||||
if !common.ComparePanelVersion(string(latestVersion), currentVersion.Value) {
|
||||
return nil, err
|
||||
}
|
||||
upgrade.LatestVersion = latestVersion
|
||||
@@ -131,26 +130,26 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
}
|
||||
global.LOG.Info("backup original data successful, now start to upgrade!")
|
||||
|
||||
if err := cpBinary([]string{tmpDir + "/1panel"}, "/usr/local/bin/1panel"); err != nil {
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel"), "/usr/local/bin"); 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 {
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1pctl"), "/usr/local/bin"); 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 {
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
u.handleRollback(fileOp, originalDir, 3)
|
||||
u.handleRollback(originalDir, 3)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,25 +180,24 @@ func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) {
|
||||
dbPath := global.CONF.System.DbPath + "/1Panel.db"
|
||||
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
if err := cpBinary([]string{originalDir + "/1Panel.db"}, dbPath); err != nil {
|
||||
if err := common.CopyFile(path.Join(originalDir, "1Panel.db"), global.CONF.System.DbPath); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
if err := cpBinary([]string{originalDir + "/1panel"}, "/usr/local/bin/1panel"); err != nil {
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1pctl failed, err: %v", err)
|
||||
}
|
||||
if errStep == 1 {
|
||||
return
|
||||
}
|
||||
if err := cpBinary([]string{originalDir + "/1pctl"}, "/usr/local/bin/1pctl"); err != nil {
|
||||
if err := common.CopyFile(path.Join(originalDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
if errStep == 2 {
|
||||
return
|
||||
}
|
||||
if err := cpBinary([]string{originalDir + "/1panel.service"}, "/etc/systemd/system/1panel.service"); err != nil {
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -250,19 +248,24 @@ func (u *UpgradeService) loadReleaseNotes(path string) (string, error) {
|
||||
}
|
||||
|
||||
func loadArch() (string, error) {
|
||||
switch runtime.GOARCH {
|
||||
case "amd64", "ppc64le", "s390x", "arm64":
|
||||
return runtime.GOARCH, nil
|
||||
case "arm":
|
||||
std, err := cmd.Exec("uname -m")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
|
||||
}
|
||||
if std == "armv7l\n" {
|
||||
return "armv7", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported such arch: arm-%s", std)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported such arch: %s", runtime.GOARCH)
|
||||
std, err := cmd.Exec("uname -a")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
|
||||
}
|
||||
if strings.Contains(std, "x86_64") {
|
||||
return "amd64", nil
|
||||
}
|
||||
if strings.Contains(std, "arm64") || strings.Contains(std, "aarch64") {
|
||||
return "arm64", nil
|
||||
}
|
||||
if strings.Contains(std, "armv7l") {
|
||||
return "armv7", nil
|
||||
}
|
||||
if strings.Contains(std, "ppc64le") {
|
||||
return "ppc64le", nil
|
||||
}
|
||||
if strings.Contains(std, "s390x") {
|
||||
return "s390x", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported such arch: %s", std)
|
||||
}
|
||||
|
||||
+132
-85
@@ -61,6 +61,7 @@ type IWebsiteService interface {
|
||||
UpdateWebsite(req request.WebsiteUpdate) error
|
||||
DeleteWebsite(req request.WebsiteDelete) error
|
||||
GetWebsite(id uint) (response.WebsiteDTO, error)
|
||||
|
||||
CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error)
|
||||
GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error)
|
||||
DeleteWebsiteDomain(domainId uint) error
|
||||
@@ -75,10 +76,6 @@ type IWebsiteService interface {
|
||||
ChangeDefaultServer(id uint) error
|
||||
PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
|
||||
|
||||
GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error)
|
||||
UpdateWafConfig(req request.WebsiteWafUpdate) error
|
||||
UpdateWafFile(req request.WebsiteWafFileUpdate) (err error)
|
||||
|
||||
GetPHPConfig(id uint) (*response.PHPConfig, error)
|
||||
UpdatePHPConfig(req request.WebsitePHPConfigUpdate) error
|
||||
UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) error
|
||||
@@ -119,7 +116,16 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
||||
}
|
||||
opts = append(opts, commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if req.Name != "" {
|
||||
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
|
||||
domains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomainLike(req.Name))
|
||||
if len(domains) > 0 {
|
||||
var websiteIds []uint
|
||||
for _, domain := range domains {
|
||||
websiteIds = append(websiteIds, domain.WebsiteID)
|
||||
}
|
||||
opts = append(opts, websiteRepo.WithIDs(websiteIds))
|
||||
} else {
|
||||
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
|
||||
}
|
||||
}
|
||||
if req.WebsiteGroupID != 0 {
|
||||
opts = append(opts, websiteRepo.WithGroupID(req.WebsiteGroupID))
|
||||
@@ -173,8 +179,6 @@ func (w WebsiteService) GetWebsites() ([]response.WebsiteDTO, error) {
|
||||
}
|
||||
|
||||
func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) {
|
||||
primaryDomainArray := strings.Split(create.PrimaryDomain, ":")
|
||||
primaryDomain := primaryDomainArray[0]
|
||||
alias := create.Alias
|
||||
if common.ContainsChinese(alias) {
|
||||
alias, err = common.PunycodeEncode(alias)
|
||||
@@ -208,7 +212,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
|
||||
defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate)
|
||||
website := &model.Website{
|
||||
PrimaryDomain: primaryDomain,
|
||||
PrimaryDomain: create.PrimaryDomain,
|
||||
Type: create.Type,
|
||||
Alias: alias,
|
||||
Remark: create.Remark,
|
||||
@@ -318,12 +322,16 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
case constant.RuntimeNode:
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = createWafConfig(website, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, ctx := helper.GetTxAndContext()
|
||||
defer tx.Rollback()
|
||||
if err = websiteRepo.Create(ctx, website); err != nil {
|
||||
@@ -416,17 +424,18 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := delNginxConfig(website, req.ForceDelete); err != nil {
|
||||
if err = delNginxConfig(website, req.ForceDelete); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = delWafConfig(website, req.ForceDelete); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if checkIsLinkApp(website) && req.DeleteApp {
|
||||
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if !reflect.DeepEqual(model.AppInstall{}, appInstall) {
|
||||
if err := deleteAppInstall(appInstall, true, req.ForceDelete, true); err != nil && !req.ForceDelete {
|
||||
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||
if appInstall.ID > 0 {
|
||||
if err = deleteAppInstall(appInstall, true, req.ForceDelete, true); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -485,6 +494,53 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(wafDataPath) {
|
||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||
content, err := fileOp.GetContent(websitesConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var websitesArray []request.WafWebsite
|
||||
if content != nil {
|
||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index, wafWebsite := range websitesArray {
|
||||
if wafWebsite.Key == website.Alias {
|
||||
wafSite := request.WafWebsite{
|
||||
Key: website.Alias,
|
||||
Domains: wafWebsite.Domains,
|
||||
Host: wafWebsite.Host,
|
||||
}
|
||||
for _, domain := range domainModels {
|
||||
wafSite.Domains = append(wafSite.Domains, domain.Domain)
|
||||
if domain.Port != 80 && domain.Port != 443 {
|
||||
wafSite.Host = append(wafSite.Host, domain.Domain+":"+string(rune(domain.Port)))
|
||||
}
|
||||
}
|
||||
if len(wafSite.Host) == 0 {
|
||||
wafSite.Host = []string{}
|
||||
}
|
||||
websitesArray[index] = wafSite
|
||||
break
|
||||
}
|
||||
}
|
||||
websitesContent, err := json.Marshal(websitesArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return domainModels, websiteDomainRepo.BatchCreate(context.TODO(), domainModels)
|
||||
}
|
||||
|
||||
@@ -514,6 +570,7 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
||||
if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithDomain(webSiteDomain.Domain)); len(oldDomains) == 1 {
|
||||
domains = append(domains, webSiteDomain.Domain)
|
||||
}
|
||||
|
||||
if len(ports) > 0 || len(domains) > 0 {
|
||||
stringBinds := make([]string, len(ports))
|
||||
for i := 0; i < len(ports); i++ {
|
||||
@@ -524,6 +581,63 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
||||
}
|
||||
}
|
||||
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(wafDataPath) {
|
||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||
content, err := fileOp.GetContent(websitesConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var websitesArray []request.WafWebsite
|
||||
var newWebsitesArray []request.WafWebsite
|
||||
if content != nil {
|
||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, wafWebsite := range websitesArray {
|
||||
if wafWebsite.Key == website.Alias {
|
||||
wafSite := wafWebsite
|
||||
oldDomains := wafSite.Domains
|
||||
var newDomains []string
|
||||
for _, domain := range oldDomains {
|
||||
if domain == webSiteDomain.Domain {
|
||||
continue
|
||||
}
|
||||
newDomains = append(newDomains, domain)
|
||||
}
|
||||
wafSite.Domains = newDomains
|
||||
oldHostArray := wafSite.Host
|
||||
var newHostArray []string
|
||||
for _, host := range oldHostArray {
|
||||
if host == webSiteDomain.Domain+":"+string(rune(webSiteDomain.Port)) {
|
||||
continue
|
||||
}
|
||||
newHostArray = append(newHostArray, host)
|
||||
}
|
||||
wafSite.Host = newHostArray
|
||||
if len(wafSite.Host) == 0 {
|
||||
wafSite.Host = []string{}
|
||||
}
|
||||
newWebsitesArray = append(newWebsitesArray, wafSite)
|
||||
} else {
|
||||
newWebsitesArray = append(newWebsitesArray, wafWebsite)
|
||||
}
|
||||
}
|
||||
websitesContent, err := json.Marshal(newWebsitesArray)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return websiteDomainRepo.DeleteBy(context.TODO(), commonRepo.WithByID(domainId))
|
||||
}
|
||||
|
||||
@@ -843,56 +957,6 @@ func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]r
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) {
|
||||
var res response.WebsiteWafConfig
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.Enable = true
|
||||
if req.Key != "" {
|
||||
params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"set"}, &website)
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
for _, param := range params {
|
||||
if param.Params[0] == req.Key {
|
||||
res.Enable = len(param.Params) > 1 && param.Params[1] == "on"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nginxFull, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
filePath := path.Join(nginxFull.SiteDir, "sites", website.Alias, "waf", "rules", req.Rule+".json")
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
res.Content = string(content)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateWafConfig(req request.WebsiteWafUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateValue := "on"
|
||||
if !req.Enable {
|
||||
updateValue = "off"
|
||||
}
|
||||
return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{
|
||||
{Name: "set", Params: []string{req.Key, updateValue}},
|
||||
}, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
@@ -1890,7 +1954,7 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
|
||||
Parameters: []string{"off"},
|
||||
})
|
||||
validDir := &components.Directive{
|
||||
Name: "valid_referers",
|
||||
Name: "valid_referrers",
|
||||
Parameters: []string{},
|
||||
}
|
||||
if req.NoneRef {
|
||||
@@ -1959,7 +2023,7 @@ func (w WebsiteService) GetAntiLeech(id uint) (*response.NginxAntiLeechRes, erro
|
||||
}
|
||||
lDirectives := location.GetBlock().GetDirectives()
|
||||
for _, lDir := range lDirectives {
|
||||
if lDir.GetName() == "valid_referers" {
|
||||
if lDir.GetName() == "valid_referrers" {
|
||||
res.Enable = true
|
||||
params := lDir.GetParameters()
|
||||
for _, param := range params {
|
||||
@@ -2330,23 +2394,6 @@ func (w WebsiteService) UpdateRedirectFile(req request.NginxRedirectUpdate) (err
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateWafFile(req request.WebsiteWafFileUpdate) (err error) {
|
||||
var (
|
||||
website model.Website
|
||||
nginxInstall model.AppInstall
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rulePath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "waf", "rules", fmt.Sprintf("%s.json", req.Type))
|
||||
return files.NewFileOp().WriteFile(rulePath, strings.NewReader(req.Content), 0755)
|
||||
}
|
||||
|
||||
func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
type WebsiteDnsAccountService struct {
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -18,6 +13,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 +117,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
|
||||
@@ -191,12 +194,12 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileOp.CopyDir(path.Join(nginxFolder, "www", "common", "waf", "rules"), path.Join(siteFolder, "waf"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime) error {
|
||||
@@ -232,12 +235,8 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
server.UpdateServerName(serverNames)
|
||||
|
||||
siteFolder := path.Join("/www", "sites", website.Alias)
|
||||
commonFolder := path.Join("/www", "common")
|
||||
server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log")})
|
||||
server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")})
|
||||
server.UpdateDirective("access_by_lua_file", []string{path.Join(commonFolder, "waf", "access.lua")})
|
||||
server.UpdateDirective("set", []string{"$RulePath", path.Join(siteFolder, "waf", "rules")})
|
||||
server.UpdateDirective("set", []string{"$logdir", path.Join(siteFolder, "log")})
|
||||
|
||||
rootIndex := path.Join("/www/sites", website.Alias, "index")
|
||||
switch website.Type {
|
||||
@@ -284,6 +283,99 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
return nil
|
||||
}
|
||||
|
||||
func createWafConfig(website *model.Website, domains []model.WebsiteDomain) error {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(wafDataPath) {
|
||||
return nil
|
||||
}
|
||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||
content, err := fileOp.GetContent(websitesConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var websitesArray []request.WafWebsite
|
||||
if len(content) != 0 {
|
||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
wafWebsite := request.WafWebsite{
|
||||
Key: website.Alias,
|
||||
Domains: make([]string, 0),
|
||||
Host: make([]string, 0),
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
wafWebsite.Domains = append(wafWebsite.Domains, domain.Domain)
|
||||
if domain.Port != 80 && domain.Port != 443 {
|
||||
wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+string(rune(domain.Port)))
|
||||
}
|
||||
}
|
||||
websitesArray = append(websitesArray, wafWebsite)
|
||||
websitesContent, err := json.Marshal(websitesArray)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
sitesDir = path.Join(wafDataPath, "sites")
|
||||
defaultConfigPath = path.Join(wafDataPath, "conf", "siteConfig.json")
|
||||
defaultRuleDir = path.Join(wafDataPath, "rules")
|
||||
websiteDir = path.Join(sitesDir, website.Alias)
|
||||
)
|
||||
|
||||
defaultConfigContent, err := fileOp.GetContent(defaultConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !fileOp.Stat(websiteDir) {
|
||||
if err = fileOp.CreateDir(websiteDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.DeleteDir(websiteDir)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = fileOp.SaveFileWithByte(path.Join(websiteDir, "config.json"), defaultConfigContent, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
websiteRuleDir := path.Join(websiteDir, "rules")
|
||||
if !fileOp.Stat(websiteRuleDir) {
|
||||
if err := fileOp.CreateDir(websiteRuleDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defaultRulesName := []string{"acl", "args", "cookie", "defaultUaBlack", "defaultUrlBlack", "fileExt", "header", "methodWhite"}
|
||||
for _, ruleName := range defaultRulesName {
|
||||
srcPath := path.Join(defaultRuleDir, ruleName+".json")
|
||||
if err = fileOp.Copy(srcPath, websiteRuleDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func delNginxConfig(website model.Website, force bool) error {
|
||||
nginxApp, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty))
|
||||
if err != nil {
|
||||
@@ -309,6 +401,7 @@ func delNginxConfig(website model.Website, force bool) error {
|
||||
}
|
||||
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", website.Alias)
|
||||
if fileOp.Stat(sitePath) {
|
||||
xpack.RemoveTamper(website.Alias)
|
||||
_ = fileOp.DeleteDir(sitePath)
|
||||
}
|
||||
|
||||
@@ -321,6 +414,52 @@ func delNginxConfig(website model.Website, force bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func delWafConfig(website model.Website, force bool) error {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(wafDataPath) {
|
||||
return nil
|
||||
}
|
||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||
content, err := fileOp.GetContent(websitesConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var websitesArray []request.WafWebsite
|
||||
var newWebsiteArray []request.WafWebsite
|
||||
if len(content) > 0 {
|
||||
if err = json.Unmarshal(content, &websitesArray); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, wafWebsite := range websitesArray {
|
||||
if wafWebsite.Key != website.Alias {
|
||||
newWebsiteArray = append(newWebsiteArray, wafWebsite)
|
||||
}
|
||||
}
|
||||
websitesContent, err := json.Marshal(newWebsiteArray)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = fileOp.DeleteDir(path.Join(wafDataPath, "sites", website.Alias))
|
||||
|
||||
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
if force {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addListenAndServerName(website model.Website, ports []int, domains []string) error {
|
||||
nginxFull, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
|
||||
@@ -47,10 +47,14 @@ func WithDetail(Key string, detail interface{}, err error) BusinessError {
|
||||
}
|
||||
|
||||
func WithErr(Key string, err error) BusinessError {
|
||||
paramMap := map[string]interface{}{}
|
||||
if err != nil {
|
||||
paramMap["err"] = err
|
||||
}
|
||||
return BusinessError{
|
||||
Msg: Key,
|
||||
Detail: "",
|
||||
Err: err,
|
||||
Msg: Key,
|
||||
Map: paramMap,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ type System struct {
|
||||
Entrance string `mapstructure:"entrance"`
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
AppRepo string `mapstructure:"app_repo"`
|
||||
ChangeUserInfo bool `mapstructure:"change_user_info"`
|
||||
ChangeUserInfo string `mapstructure:"change_user_info"`
|
||||
OneDriveID string `mapstructure:"one_drive_id"`
|
||||
OneDriveSc string `mapstructure:"one_drive_sc"`
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
Syncing = "Syncing"
|
||||
SyncSuccess = "SyncSuccess"
|
||||
Paused = "Paused"
|
||||
SyncErr = "SyncErr"
|
||||
|
||||
ContainerPrefix = "1Panel-"
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ const (
|
||||
CodeErrDomain = 409
|
||||
CodeErrInternalServer = 500
|
||||
CodeErrHeader = 406
|
||||
|
||||
CodeErrXpack = 410
|
||||
)
|
||||
|
||||
// internal
|
||||
@@ -43,6 +45,9 @@ var (
|
||||
ErrNameIsExist = "ErrNameIsExist"
|
||||
ErrDemoEnvironment = "ErrDemoEnvironment"
|
||||
ErrCmdIllegal = "ErrCmdIllegal"
|
||||
ErrXpackNotFound = "ErrXpackNotFound"
|
||||
ErrXpackNotActive = "ErrXpackNotActive"
|
||||
ErrXpackOutOfDate = "ErrXpackOutOfDate"
|
||||
)
|
||||
|
||||
// app
|
||||
@@ -62,6 +67,7 @@ var (
|
||||
ErrAppNameExist = "ErrAppNameExist"
|
||||
ErrFileNotFound = "ErrFileNotFound"
|
||||
ErrFileParseApp = "ErrFileParseApp"
|
||||
ErrAppParamKey = "ErrAppParamKey"
|
||||
)
|
||||
|
||||
// website
|
||||
@@ -149,3 +155,11 @@ var (
|
||||
var (
|
||||
ErrNotExistUser = "ErrNotExistUser"
|
||||
)
|
||||
|
||||
// license
|
||||
var (
|
||||
ErrLicense = "ErrLicense"
|
||||
ErrLicenseCheck = "ErrLicenseCheck"
|
||||
ErrLicenseSave = "ErrLicenseSave"
|
||||
ErrLicenseSync = "ErrLicenseSync"
|
||||
)
|
||||
|
||||
@@ -13,13 +13,14 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
DB *gorm.DB
|
||||
LOG *logrus.Logger
|
||||
CONF configs.ServerConfig
|
||||
VALID *validator.Validate
|
||||
SESSION *psession.PSession
|
||||
CACHE *badger_db.Cache
|
||||
Viper *viper.Viper
|
||||
DB *gorm.DB
|
||||
MonitorDB *gorm.DB
|
||||
LOG *logrus.Logger
|
||||
CONF configs.ServerConfig
|
||||
VALID *validator.Validate
|
||||
SESSION *psession.PSession
|
||||
CACHE *badger_db.Cache
|
||||
Viper *viper.Viper
|
||||
|
||||
Cron *cron.Cron
|
||||
MonitorCronID cron.EntryID
|
||||
|
||||
@@ -60,6 +60,7 @@ 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"
|
||||
@@ -73,6 +74,7 @@ ErrFileDownloadDir: "Download folder not supported"
|
||||
ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first"
|
||||
ErrSourcePathNotFound: "Source directory does not exist"
|
||||
ErrFavoriteExist: "This path has been collected"
|
||||
ErrInvalidChar: "Illegal characters are prohibited"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "Domain is already exist"
|
||||
@@ -143,7 +145,7 @@ ErrNodeModulesNotFound: "The node_modules folder does not exist! Please edit the
|
||||
#setting
|
||||
ErrBackupInUsed: "The backup account is already being used in a cronjob and cannot be deleted."
|
||||
ErrBackupCheck: "Backup account test connection failed {{ .err}}"
|
||||
ErrOSSConn: "Unable to successfully request the latest version. Please check if the server can connect to the external network environment."
|
||||
ErrOSSConn: "Unable to retrieve the latest version, please check if the server can connect to the external network."
|
||||
ErrEntrance: "Security entrance information error. Please check and try again!"
|
||||
|
||||
#tool
|
||||
@@ -165,3 +167,23 @@ CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{
|
||||
#toolbox
|
||||
ErrNotExistUser: "The current user does not exist. Please modify and retry!"
|
||||
ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, please check and try again!"
|
||||
|
||||
#waf
|
||||
ErrScope: "Modification of this configuration is not supported"
|
||||
ErrStateChange: "State modification failed"
|
||||
ErrRuleExist: "Rule is Exist"
|
||||
ErrRuleNotExist: "Rule is not Exist"
|
||||
ErrParseIP: "IP format error"
|
||||
ErrDefaultIP: "default is a reserved name, please change it to another name"
|
||||
ErrGroupInUse: "The IP group is used by the black/white list and cannot be deleted"
|
||||
ErrGroupExist: "IP group name already exists"
|
||||
ErrIPRange: "Wrong IP range"
|
||||
|
||||
#license
|
||||
ErrLicense: "License format error, please check and try again!"
|
||||
ErrLicenseCheck: "License verification failed, please check and try again!"
|
||||
ErrLicenseSave: "Failed to save license information, error {{ .err }}, please try again!"
|
||||
ErrLicenseSync: "Failed to sync license information, no license information detected in the database!"
|
||||
ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface"
|
||||
ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface"
|
||||
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
|
||||
|
||||
@@ -23,6 +23,7 @@ ErrTypePortRange: '連接埠範圍需要在 1-65535 之間'
|
||||
Success: "成功"
|
||||
Failed: "失敗"
|
||||
SystemRestart: "系統重啟導致任務中斷"
|
||||
ErrInvalidChar: "禁止使用非法字元"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
|
||||
@@ -60,6 +61,7 @@ ErrAppNameExist: '應用名稱已存在'
|
||||
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
|
||||
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
|
||||
ErrAppWarn: "狀態異常,請查看日誌"
|
||||
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
@@ -144,7 +146,7 @@ ErrNodeModulesNotFound: "node_modules 文件夾不存在!請編輯運行環境
|
||||
#setting
|
||||
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||
ErrBackupCheck: "備份帳號測試連接失敗 {{ .err}}"
|
||||
ErrOSSConn: "無法成功請求最新版本,請檢查伺服器是否能夠連接到外部網絡環境。"
|
||||
ErrOSSConn: "無法獲取最新版本,請確認伺服器是否能夠連接外部網路。"
|
||||
ErrEntrance: "安全入口信息錯誤,請檢查後重試!"
|
||||
|
||||
#tool
|
||||
@@ -166,3 +168,23 @@ CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .p
|
||||
#toolbox
|
||||
ErrNotExistUser: "當前使用者不存在,請修改後重試!"
|
||||
ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支援修改此配置"
|
||||
ErrStateChange: "狀態修改失敗"
|
||||
ErrRuleExist: "規則名稱已存在"
|
||||
ErrRuleNotExist: "規則不存在"
|
||||
ErrParseIP: "IP 格式錯誤"
|
||||
ErrDefaultIP: "default 為保留名稱,請更換其他名稱"
|
||||
ErrGroupInUse: "IP 群組被黑/白名單使用,無法刪除"
|
||||
ErrGroupExist: "IP 群組名稱已存在"
|
||||
ErrIPRange: "IP 範圍錯誤"
|
||||
|
||||
#license
|
||||
ErrLicense: "許可證格式錯誤,請檢查後重試!"
|
||||
ErrLicenseCheck: "許可證校驗失敗,請檢查後重試!"
|
||||
ErrLicenseSave: "許可證信息保存失敗,錯誤 {{ .err }}, 請重試!"
|
||||
ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證信息!"
|
||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
|
||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
|
||||
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
|
||||
|
||||
@@ -60,6 +60,7 @@ ErrAppNameExist: '应用名称已存在'
|
||||
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
|
||||
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
|
||||
ErrAppWarn: "状态异常,请查看日志"
|
||||
ErrAppParamKey: "参数 {{ .name }} 字段异常"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
@@ -73,6 +74,7 @@ ErrFileDownloadDir: "不支持下载文件夹"
|
||||
ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令"
|
||||
ErrSourcePathNotFound: "源目录不存在"
|
||||
ErrFavoriteExist: "已收藏此路径"
|
||||
ErrInvalidChar: "禁止使用非法字符"
|
||||
|
||||
#website
|
||||
ErrDomainIsExist: "域名已存在"
|
||||
@@ -143,7 +145,7 @@ ErrNodeModulesNotFound: "node_modules 文件夹不存在!请编辑运行环境
|
||||
#setting
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
ErrBackupCheck: "备份账号测试连接失败 {{ .err}}"
|
||||
ErrOSSConn: "无法成功请求最新版本,请检查服务器是否能够连接到外部网络环境。"
|
||||
ErrOSSConn: "无法获取最新版本,请确认服务器是否能够连接外部网络。"
|
||||
ErrEntrance: "安全入口信息错误,请检查后重试!"
|
||||
|
||||
#tool
|
||||
@@ -164,4 +166,25 @@ CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .p
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: "当前用户不存在,请修改后重试!"
|
||||
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
|
||||
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
|
||||
|
||||
#waf
|
||||
ErrScope: "不支持修改此配置"
|
||||
ErrStateChange: "状态修改失败"
|
||||
ErrRuleExist: "规则名称已存在"
|
||||
ErrRuleNotExist: "规则不存在"
|
||||
ErrParseIP: "IP 格式错误"
|
||||
ErrDefaultIP: "default 为保留名称,请更换其他名称"
|
||||
ErrGroupInUse: "IP 组被黑/白名单使用,无法删除"
|
||||
ErrGroupExist: "IP 组名称已存在"
|
||||
ErrIPRange: "IP 范围错误"
|
||||
|
||||
#license
|
||||
ErrLicense: "许可证格式错误,请检查后重试!"
|
||||
ErrLicenseCheck: "许可证校验失败,请检查后重试!"
|
||||
ErrLicenseSave: "许可证信息保存失败,错误 {{ .err }},请重试!"
|
||||
ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证信息!"
|
||||
ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可证 界面导入许可证"
|
||||
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
|
||||
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
|
||||
|
||||
|
||||
+37
-5
@@ -4,14 +4,13 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
@@ -36,6 +35,7 @@ func Init() {
|
||||
Colorful: false,
|
||||
},
|
||||
)
|
||||
initMonitorDB(newLogger)
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(fullPath), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
@@ -47,7 +47,7 @@ func Init() {
|
||||
_ = db.Exec("PRAGMA journal_mode = WAL;")
|
||||
sqlDB, dbError := db.DB()
|
||||
if dbError != nil {
|
||||
panic(err)
|
||||
panic(dbError)
|
||||
}
|
||||
sqlDB.SetConnMaxIdleTime(10)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
@@ -56,3 +56,35 @@ func Init() {
|
||||
global.DB = db
|
||||
global.LOG.Info("init db successfully")
|
||||
}
|
||||
|
||||
func initMonitorDB(newLogger logger.Interface) {
|
||||
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 failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
fullPath := path.Join(global.CONF.System.DbPath, "monitor.db")
|
||||
if _, err := os.Stat(fullPath); err != nil {
|
||||
if _, err := os.Create(fullPath); err != nil {
|
||||
panic(fmt.Errorf("init db file failed, err: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(fullPath), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: newLogger,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlDB, dbError := db.DB()
|
||||
if dbError != nil {
|
||||
panic(dbError)
|
||||
}
|
||||
sqlDB.SetConnMaxIdleTime(10)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
global.MonitorDB = db
|
||||
global.LOG.Info("init monitor db successfully")
|
||||
}
|
||||
|
||||
+40
-15
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
@@ -57,21 +58,7 @@ func Init() {
|
||||
global.LOG.Fatalf("init service before start failed, err: %v", err)
|
||||
}
|
||||
|
||||
if global.CONF.System.ChangeUserInfo {
|
||||
if err := settingRepo.Update("UserName", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init username before start failed, err: %v", err)
|
||||
}
|
||||
pass, _ := encrypt.StringEncrypt(common.RandStrAndNum(10))
|
||||
if err := settingRepo.Update("Password", pass); err != nil {
|
||||
global.LOG.Fatalf("init password before start failed, err: %v", err)
|
||||
}
|
||||
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
|
||||
}
|
||||
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=true/d' /usr/local/bin/1pctl", sudo)
|
||||
}
|
||||
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
|
||||
|
||||
handleCronjobStatus()
|
||||
handleSnapStatus()
|
||||
@@ -173,3 +160,41 @@ func loadLocalDir() {
|
||||
}
|
||||
global.LOG.Errorf("error type dir: %T", varMap["dir"])
|
||||
}
|
||||
|
||||
func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
||||
if len(tags) == 0 {
|
||||
return
|
||||
}
|
||||
if tags == "all" {
|
||||
if err := settingRepo.Update("UserName", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init username before start failed, err: %v", err)
|
||||
}
|
||||
pass, _ := encrypt.StringEncrypt(common.RandStrAndNum(10))
|
||||
if err := settingRepo.Update("Password", pass); err != nil {
|
||||
global.LOG.Fatalf("init password before start failed, err: %v", err)
|
||||
}
|
||||
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if strings.Contains(global.CONF.System.ChangeUserInfo, "username") {
|
||||
if err := settingRepo.Update("UserName", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init username before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if strings.Contains(global.CONF.System.ChangeUserInfo, "password") {
|
||||
pass, _ := encrypt.StringEncrypt(common.RandStrAndNum(10))
|
||||
if err := settingRepo.Update("Password", pass); err != nil {
|
||||
global.LOG.Fatalf("init password before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if strings.Contains(global.CONF.System.ChangeUserInfo, "entrance") {
|
||||
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
|
||||
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", sudo, global.CONF.System.ChangeUserInfo)
|
||||
}
|
||||
|
||||
@@ -72,6 +72,15 @@ func Init() {
|
||||
migrations.UpdateSnapshotRecords,
|
||||
|
||||
migrations.UpdateWebDavConf,
|
||||
|
||||
migrations.AddSnapshotIgnore,
|
||||
migrations.AddDatabaseIsDelete,
|
||||
migrations.AddXpackHideMenu,
|
||||
migrations.AddCronjobCommand,
|
||||
migrations.NewMonitorDB,
|
||||
migrations.AddNoAuthSetting,
|
||||
migrations.UpdateXpackHideMenu,
|
||||
migrations.AddMenuTabsSetting,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var AddSnapshotIgnore = &gormigrate.Migration{
|
||||
ID: "20240311-add-snapshot-ignore",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "SnapshotIgnore", Value: "*.sock"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddDatabaseIsDelete = &gormigrate.Migration{
|
||||
ID: "20240314-add-database-is-delete",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.DatabaseMysql{}, &model.DatabasePostgresql{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddXpackHideMenu = &gormigrate.Migration{
|
||||
ID: "20240328-add-xpack-hide-menu",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "XpackHideMenu", Value: "{\"id\":\"1\",\"label\":\"/xpack\",\"isCheck\":true,\"title\":\"xpack.menu\",\"children\":[{\"id\":\"2\",\"title\":\"xpack.waf.name\",\"path\":\"/xpack/waf/dashboard\",\"label\":\"Dashboard\",\"isCheck\":true},{\"id\":\"3\",\"title\":\"xpack.tamper.tamper\",\"path\":\"/xpack/tamper\",\"label\":\"Tamper\",\"isCheck\":true},{\"id\":\"4\",\"title\":\"xpack.setting.setting\",\"path\":\"/xpack/setting\",\"label\":\"XSetting\",\"isCheck\":true}]}"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddCronjobCommand = &gormigrate.Migration{
|
||||
ID: "20240403-add-cronjob-command",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Cronjob{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var NewMonitorDB = &gormigrate.Migration{
|
||||
ID: "20240408-new-monitor-db",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var (
|
||||
bases []model.MonitorBase
|
||||
ios []model.MonitorIO
|
||||
networks []model.MonitorNetwork
|
||||
)
|
||||
_ = tx.Find(&bases).Error
|
||||
_ = tx.Find(&ios).Error
|
||||
_ = tx.Find(&networks).Error
|
||||
|
||||
if err := global.MonitorDB.AutoMigrate(&model.MonitorBase{}, &model.MonitorIO{}, &model.MonitorNetwork{}); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = global.MonitorDB.Exec("DELETE FROM monitor_bases").Error
|
||||
_ = global.MonitorDB.Exec("DELETE FROM monitor_ios").Error
|
||||
_ = global.MonitorDB.Exec("DELETE FROM monitor_networks").Error
|
||||
|
||||
if len(bases) != 0 {
|
||||
for i := 0; i <= len(bases)/200; i++ {
|
||||
var itemData []model.MonitorBase
|
||||
if 200*(i+1) <= len(bases) {
|
||||
itemData = bases[200*i : 200*(i+1)]
|
||||
} else {
|
||||
itemData = bases[200*i:]
|
||||
}
|
||||
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(ios) != 0 {
|
||||
for i := 0; i <= len(ios)/200; i++ {
|
||||
var itemData []model.MonitorIO
|
||||
if 200*(i+1) <= len(ios) {
|
||||
itemData = ios[200*i : 200*(i+1)]
|
||||
} else {
|
||||
itemData = ios[200*i:]
|
||||
}
|
||||
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(networks) != 0 {
|
||||
for i := 0; i <= len(networks)/200; i++ {
|
||||
var itemData []model.MonitorNetwork
|
||||
if 200*(i+1) <= len(networks) {
|
||||
itemData = networks[200*i : 200*(i+1)]
|
||||
} else {
|
||||
itemData = networks[200*i:]
|
||||
}
|
||||
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddNoAuthSetting = &gormigrate.Migration{
|
||||
ID: "20240328-add-no-auth-setting",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "NoAuthSetting", Value: "200"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateXpackHideMenu = &gormigrate.Migration{
|
||||
ID: "20240411-update-xpack-hide-menu",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": "{\"id\":\"1\",\"label\":\"/xpack\",\"isCheck\":true,\"title\":\"xpack.menu\",\"children\":[{\"id\":\"2\",\"title\":\"xpack.waf.name\",\"path\":\"/xpack/waf/dashboard\",\"label\":\"Dashboard\",\"isCheck\":true},{\"id\":\"3\",\"title\":\"xpack.tamper.tamper\",\"path\":\"/xpack/tamper\",\"label\":\"Tamper\",\"isCheck\":true},{\"id\":\"4\",\"title\":\"xpack.gpu.gpu\",\"path\":\"/xpack/gpu\",\"label\":\"GPU\",\"isCheck\":true},{\"id\":\"5\",\"title\":\"xpack.setting.setting\",\"path\":\"/xpack/setting\",\"label\":\"XSetting\",\"isCheck\":true}]}"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMenuTabsSetting = &gormigrate.Migration{
|
||||
ID: "20240415-add-menu-tabs-setting",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "MenuTabs", Value: "disable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -124,6 +124,12 @@ var AddTablePHPExtensions = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "默认", Extensions: "bcmath,gd,gettext,intl,pcntl,shmop,soap,sockets,sysvsem,xmlrpc,zip"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "苹果CMS-V10", Extensions: "mysqli,pdo_mysql,zip,gd,redis,memcache,memcached"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.PHPExtensions{Name: "SeaCMS", Extensions: "mysqli,pdo_mysql,gd,curl"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
@@ -12,7 +15,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -21,6 +23,7 @@ var (
|
||||
|
||||
func setWebStatic(rootRouter *gin.RouterGroup) {
|
||||
rootRouter.StaticFS("/public", http.FS(web.Favicon))
|
||||
rootRouter.Static("/api/v1/images", "./uploads")
|
||||
rootRouter.Use(func(c *gin.Context) {
|
||||
c.Next()
|
||||
})
|
||||
@@ -61,6 +64,8 @@ func Routers() *gin.Engine {
|
||||
PublicGroup.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, "ok")
|
||||
})
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
PublicGroup.GET("/api/v1/respagecode", baseApi.GetResponsePage)
|
||||
PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
setWebStatic(PublicGroup)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func Init() {
|
||||
global.CONF.System.Username = username
|
||||
global.CONF.System.Password = password
|
||||
global.CONF.System.Entrance = entrance
|
||||
global.CONF.System.ChangeUserInfo = loadChange()
|
||||
global.CONF.System.ChangeUserInfo = loadChangeInfo()
|
||||
global.Viper = v
|
||||
}
|
||||
|
||||
@@ -112,10 +112,10 @@ func loadParams(param string) string {
|
||||
return info
|
||||
}
|
||||
|
||||
func loadChange() bool {
|
||||
func loadChangeInfo() string {
|
||||
stdout, err := cmd.Exec("grep '^CHANGE_USER_INFO=' /usr/bin/1pctl | cut -d'=' -f2")
|
||||
if err != nil {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
return stdout == "true\n"
|
||||
return strings.ReplaceAll(stdout, "\n", "")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ func commonGroups() []CommonRouter {
|
||||
&DashboardRouter{},
|
||||
&HostRouter{},
|
||||
&ContainerRouter{},
|
||||
&MonitorRouter{},
|
||||
&LogRouter{},
|
||||
&FileRouter{},
|
||||
&ToolboxRouter{},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -35,6 +35,11 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/firewall/update/addr", baseApi.UpdateAddrRule)
|
||||
hostRouter.POST("/firewall/update/description", baseApi.UpdateFirewallDescription)
|
||||
|
||||
hostRouter.POST("/monitor/search", baseApi.LoadMonitor)
|
||||
hostRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
||||
hostRouter.GET("/monitor/netoptions", baseApi.GetNetworkOptions)
|
||||
hostRouter.GET("/monitor/iooptions", baseApi.GetIOOptions)
|
||||
|
||||
hostRouter.GET("/ssh/conf", baseApi.LoadSSHConf)
|
||||
hostRouter.POST("/ssh/search", baseApi.GetSSHInfo)
|
||||
hostRouter.POST("/ssh/update", baseApi.UpdateSSH)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MonitorRouter struct{}
|
||||
|
||||
func (s *MonitorRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
monitorRouter := Router.Group("monitors").
|
||||
Use(middleware.JwtAuth()).
|
||||
Use(middleware.SessionAuth()).
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
monitorRouter.POST("/search", baseApi.LoadMonitor)
|
||||
monitorRouter.GET("/netoptions", baseApi.GetNetworkOptions)
|
||||
monitorRouter.GET("/iooptions", baseApi.GetIOOptions)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
groupRouter.POST("/del", baseApi.DeleteRuntime)
|
||||
groupRouter.POST("/update", baseApi.UpdateRuntime)
|
||||
groupRouter.GET("/:id", baseApi.GetRuntime)
|
||||
groupRouter.POST("/sync", baseApi.SyncStatus)
|
||||
|
||||
groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript)
|
||||
groupRouter.POST("/operate", baseApi.OperateRuntime)
|
||||
|
||||
@@ -23,13 +23,13 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
|
||||
settingRouter.POST("/update", baseApi.UpdateSetting)
|
||||
settingRouter.GET("/interface", baseApi.LoadInterfaceAddr)
|
||||
settingRouter.POST("/menu/update", baseApi.UpdateMenu)
|
||||
settingRouter.POST("/bind/update", baseApi.UpdateBindInfo)
|
||||
settingRouter.POST("/port/update", baseApi.UpdatePort)
|
||||
settingRouter.POST("/ssl/update", baseApi.UpdateSSL)
|
||||
settingRouter.GET("/ssl/info", baseApi.LoadFromCert)
|
||||
settingRouter.POST("/ssl/download", baseApi.DownloadSSL)
|
||||
settingRouter.POST("/password/update", baseApi.UpdatePassword)
|
||||
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
||||
settingRouter.POST("/mfa", baseApi.LoadMFA)
|
||||
settingRouter.POST("/mfa/bind", baseApi.MFABind)
|
||||
|
||||
|
||||
@@ -39,10 +39,6 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
groupRouter.GET("/:id/https", baseApi.GetHTTPSConfig)
|
||||
groupRouter.POST("/:id/https", baseApi.UpdateHTTPSConfig)
|
||||
|
||||
groupRouter.POST("/waf/config", baseApi.GetWebsiteWafConfig)
|
||||
groupRouter.POST("/waf/update", baseApi.UpdateWebsiteWafConfig)
|
||||
groupRouter.POST("/waf/file/update", baseApi.UpdateWebsiteWafFile)
|
||||
|
||||
groupRouter.GET("/php/config/:id", baseApi.GetWebsitePHPConfig)
|
||||
groupRouter.POST("/php/config", baseApi.UpdateWebsitePHPConfig)
|
||||
groupRouter.POST("/php/update", baseApi.UpdatePHPFile)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
)
|
||||
|
||||
type localClient struct {
|
||||
@@ -54,9 +54,8 @@ func (c localClient) Upload(src, target string) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("\\cp -f %s %s", src, path.Join(c.dir, target))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
|
||||
if err := common.CopyFile(src, target); err != nil {
|
||||
return false, fmt.Errorf("cp file failed, err: %v", err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -73,9 +72,8 @@ func (c localClient) Download(src, target string) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("\\cp -f %s %s", localPath, target)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
|
||||
if err := common.CopyFile(localPath, target); err != nil {
|
||||
return false, fmt.Errorf("cp file failed, err: %v", err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -120,10 +120,12 @@ func (m minIoClient) Download(src, target string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer object.Close()
|
||||
localFile, err := os.Create(target)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer localFile.Close()
|
||||
if _, err = io.Copy(localFile, object); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
|
||||
@@ -116,7 +118,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 +302,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
|
||||
@@ -321,7 +323,6 @@ func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string,
|
||||
if err := o.client.Do(context.Background(), req, false, &response); err != nil {
|
||||
return false, fmt.Errorf("do request for list failed, err: %v", err)
|
||||
}
|
||||
fmt.Println(response)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -362,7 +363,13 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
|
||||
splitCount += 1
|
||||
}
|
||||
bfReader := bufio.NewReader(file)
|
||||
var fileUploadResp *UploadSessionUploadResponse
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Minute * 10,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
|
||||
length, err := bfReader.Read(buffer)
|
||||
if err != nil {
|
||||
@@ -376,12 +383,15 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := o.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
|
||||
res, err := httpClient.Do(sessionFileUploadReq)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if fileUploadResp.Id == "" {
|
||||
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
|
||||
if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 {
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return false, errors.New(string(data))
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
+53
-40
@@ -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
|
||||
}
|
||||
|
||||
@@ -165,7 +177,8 @@ func CheckIllegal(args ...string) bool {
|
||||
for _, arg := range args {
|
||||
if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
|
||||
strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
|
||||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") {
|
||||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") ||
|
||||
strings.Contains(arg, "\n") || strings.Contains(arg, "\r") || strings.Contains(arg, ">") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -43,6 +45,35 @@ func CompareVersion(version1, version2 string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func ComparePanelVersion(version1, version2 string) bool {
|
||||
if version1 == version2 {
|
||||
return false
|
||||
}
|
||||
version1s := strings.Split(version1, ".")
|
||||
version2s := strings.Split(version2, ".")
|
||||
|
||||
if len(version2s) > len(version1s) {
|
||||
for i := 0; i < len(version2s)-len(version1s); i++ {
|
||||
version1s = append(version1s, "0")
|
||||
}
|
||||
}
|
||||
if len(version1s) > len(version2s) {
|
||||
for i := 0; i < len(version1s)-len(version2s); i++ {
|
||||
version2s = append(version2s, "0")
|
||||
}
|
||||
}
|
||||
|
||||
n := min(len(version1s), len(version2s))
|
||||
for i := 0; i < n; i++ {
|
||||
if version1s[i] == version2s[i] {
|
||||
continue
|
||||
} else {
|
||||
return version1s[i] > version2s[i]
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func extractNumbers(version string) []string {
|
||||
var numbers []string
|
||||
start := -1
|
||||
@@ -82,6 +113,36 @@ func GetSortedVersions(versions []string) []string {
|
||||
return versions
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
if path.Base(src) != path.Base(dst) {
|
||||
dst = path.Join(dst, path.Base(src))
|
||||
}
|
||||
if _, err := os.Stat(path.Dir(dst)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(path.Dir(dst), os.ModePerm)
|
||||
}
|
||||
}
|
||||
target, err := os.OpenFile(dst+"_temp", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer target.Close()
|
||||
|
||||
if _, err = io.Copy(target, source); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Rename(dst+"_temp", dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsCrossVersion(version1, version2 string) bool {
|
||||
version1s := strings.Split(version1, ".")
|
||||
version2s := strings.Split(version2, ".")
|
||||
|
||||
@@ -54,7 +54,17 @@ func (f FileOp) GetContent(dst string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (f FileOp) CreateDir(dst string, mode fs.FileMode) error {
|
||||
return f.Fs.MkdirAll(dst, mode)
|
||||
if err := f.Fs.MkdirAll(dst, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
modStr := fmt.Sprintf("%o", mode)
|
||||
|
||||
modeInt, err := strconv.ParseInt(modStr, 10, 64)
|
||||
if err != nil {
|
||||
modeInt = 0755
|
||||
}
|
||||
|
||||
return f.ChmodR(dst, modeInt, true)
|
||||
}
|
||||
|
||||
func (f FileOp) CreateFile(dst string) error {
|
||||
@@ -64,6 +74,14 @@ func (f FileOp) CreateFile(dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) CreateFileWithMode(dst string, mode fs.FileMode) error {
|
||||
file, err := f.Fs.OpenFile(dst, os.O_CREATE, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
func (f FileOp) LinkFile(source string, dst string, isSymlink bool) error {
|
||||
if isSymlink {
|
||||
osFs := afero.OsFs{}
|
||||
@@ -126,6 +144,21 @@ func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) SaveFileWithByte(dst string, content []byte, mode fs.FileMode) error {
|
||||
if !f.Stat(path.Dir(dst)) {
|
||||
_ = f.CreateDir(path.Dir(dst), mode.Perm())
|
||||
}
|
||||
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.Write(content)
|
||||
write.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -134,7 +167,7 @@ func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
|
||||
if cmd.HasNoPasswordSudo() {
|
||||
cmdStr = fmt.Sprintf("sudo %s", cmdStr)
|
||||
}
|
||||
if msg, err := cmd.ExecWithTimeOut(cmdStr, 2*time.Second); err != nil {
|
||||
if msg, err := cmd.ExecWithTimeOut(cmdStr, 10*time.Second); err != nil {
|
||||
if msg != "" {
|
||||
return errors.New(msg)
|
||||
}
|
||||
@@ -151,7 +184,7 @@ func (f FileOp) ChmodR(dst string, mode int64, sub bool) error {
|
||||
if cmd.HasNoPasswordSudo() {
|
||||
cmdStr = fmt.Sprintf("sudo %s", cmdStr)
|
||||
}
|
||||
if msg, err := cmd.ExecWithTimeOut(cmdStr, 2*time.Second); err != nil {
|
||||
if msg, err := cmd.ExecWithTimeOut(cmdStr, 10*time.Second); err != nil {
|
||||
if msg != "" {
|
||||
return errors.New(msg)
|
||||
}
|
||||
@@ -299,7 +332,7 @@ func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
||||
coverFlag = "-f"
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf(`mv %s "%s" "%s"`, coverFlag, p, dstPath)
|
||||
cmdStr := fmt.Sprintf(`mv %s '%s' '%s'`, coverFlag, p, dstPath)
|
||||
if err := cmd.ExecCmd(cmdStr); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -308,7 +341,7 @@ func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
||||
}
|
||||
|
||||
func (f FileOp) Mv(oldPath, dstPath string) error {
|
||||
cmdStr := fmt.Sprintf(`mv "%s" "%s"`, oldPath, dstPath)
|
||||
cmdStr := fmt.Sprintf(`mv '%s' '%s'`, oldPath, dstPath)
|
||||
if err := cmd.ExecCmd(cmdStr); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -362,13 +395,13 @@ func (f FileOp) CopyAndReName(src, dst, name string, cover bool) error {
|
||||
if name != "" && !cover {
|
||||
dstPath = filepath.Join(dst, name)
|
||||
}
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -rf "%s" "%s"`, src, dstPath))
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -rf '%s' '%s'`, src, dstPath))
|
||||
} else {
|
||||
dstPath := filepath.Join(dst, name)
|
||||
if cover {
|
||||
dstPath = dst
|
||||
}
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -f "%s" "%s"`, src, dstPath))
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -f '%s' '%s'`, src, dstPath))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,12 +414,12 @@ func (f FileOp) CopyDir(src, dst string) error {
|
||||
if err = f.Fs.MkdirAll(dstDir, srcInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -rf "%s" "%s"`, src, dst+"/"))
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -rf '%s' '%s'`, src, dst+"/"))
|
||||
}
|
||||
|
||||
func (f FileOp) CopyFile(src, dst string) error {
|
||||
dst = filepath.Clean(dst) + string(filepath.Separator)
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -f "%s" "%s"`, src, dst+"/"))
|
||||
return cmd.ExecCmd(fmt.Sprintf(`cp -f '%s' '%s'`, src, dst+"/"))
|
||||
}
|
||||
|
||||
func (f FileOp) GetDirSize(path string) (float64, error) {
|
||||
|
||||
@@ -311,20 +311,22 @@ func (f *FileInfo) listChildren(option FileOption) error {
|
||||
}
|
||||
|
||||
func (f *FileInfo) getContent() error {
|
||||
if f.Size <= 10*1024*1024 {
|
||||
afs := &afero.Afero{Fs: f.Fs}
|
||||
cByte, err := afs.ReadFile(f.Path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(cByte) > 0 && DetectBinary(cByte) {
|
||||
return buserr.New(constant.ErrFileCanNotRead)
|
||||
}
|
||||
f.Content = string(cByte)
|
||||
return nil
|
||||
} else {
|
||||
if IsBlockDevice(f.FileMode) {
|
||||
return buserr.New(constant.ErrFileCanNotRead)
|
||||
}
|
||||
if f.Size > 10*1024*1024 {
|
||||
return buserr.New("ErrFileToLarge")
|
||||
}
|
||||
afs := &afero.Afero{Fs: f.Fs}
|
||||
cByte, err := afs.ReadFile(f.Path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(cByte) > 0 && DetectBinary(cByte) {
|
||||
return buserr.New(constant.ErrFileCanNotRead)
|
||||
}
|
||||
f.Content = string(cByte)
|
||||
return nil
|
||||
}
|
||||
|
||||
func DetectBinary(buf []byte) bool {
|
||||
|
||||
@@ -2,12 +2,15 @@ package files
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/spf13/afero"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -15,6 +18,10 @@ func IsSymlink(mode os.FileMode) bool {
|
||||
return mode&os.ModeSymlink != 0
|
||||
}
|
||||
|
||||
func IsBlockDevice(mode os.FileMode) bool {
|
||||
return mode&os.ModeDevice != 0 && mode&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
func GetMimeType(path string) string {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
@@ -87,16 +94,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,11 +115,37 @@ 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
|
||||
}
|
||||
|
||||
func GetParentMode(path string) (os.FileMode, error) {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for {
|
||||
fileInfo, err := os.Stat(absPath)
|
||||
if err == nil {
|
||||
return fileInfo.Mode() & os.ModePerm, nil
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
parentDir := filepath.Dir(absPath)
|
||||
if parentDir == absPath {
|
||||
return 0, fmt.Errorf("no existing directory found in the path: %s", path)
|
||||
}
|
||||
absPath = parentDir
|
||||
}
|
||||
}
|
||||
|
||||
func IsInvalidChar(name string) bool {
|
||||
if strings.Contains(name, "&") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -70,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"`
|
||||
@@ -79,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"`
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ parsingloop:
|
||||
case p.curTokenIs(flag.LuaCode):
|
||||
context.IsLuaBlock = true
|
||||
context.LiteralCode = p.currentToken.Literal
|
||||
case p.curTokenIs(flag.Keyword):
|
||||
case p.curTokenIs(flag.Keyword) || p.curTokenIs(flag.QuotedString):
|
||||
s, err := p.parseStatement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
func TestPs(t *testing.T) {
|
||||
processes, err := process.Processes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, pro := range processes {
|
||||
var (
|
||||
name string
|
||||
parentID int32
|
||||
userName string
|
||||
status string
|
||||
startTime string
|
||||
numThreads int32
|
||||
numConnections int
|
||||
cpuPercent float64
|
||||
//mem string
|
||||
rss string
|
||||
ioRead string
|
||||
ioWrite string
|
||||
)
|
||||
name, _ = pro.Name()
|
||||
parentID, _ = pro.Ppid()
|
||||
userName, _ = pro.Username()
|
||||
array, err := pro.Status()
|
||||
if err == nil {
|
||||
status = array[0]
|
||||
}
|
||||
createTime, err := pro.CreateTime()
|
||||
if err == nil {
|
||||
t := time.Unix(createTime/1000, 0)
|
||||
startTime = t.Format("2006-1-2 15:04:05")
|
||||
}
|
||||
numThreads, _ = pro.NumThreads()
|
||||
connections, err := pro.Connections()
|
||||
if err == nil && len(connections) > 0 {
|
||||
numConnections = len(connections)
|
||||
}
|
||||
cpuPercent, _ = pro.CPUPercent()
|
||||
menInfo, err := pro.MemoryInfo()
|
||||
if err == nil {
|
||||
rssF := float64(menInfo.RSS) / 1048576
|
||||
rss = fmt.Sprintf("%.2f", rssF)
|
||||
}
|
||||
ioStat, err := pro.IOCounters()
|
||||
if err == nil {
|
||||
ioWrite = strconv.FormatUint(ioStat.WriteBytes, 10)
|
||||
ioRead = strconv.FormatUint(ioStat.ReadBytes, 10)
|
||||
}
|
||||
|
||||
cmdLine, err := pro.Cmdline()
|
||||
if err == nil {
|
||||
fmt.Println(cmdLine)
|
||||
}
|
||||
ss, err := pro.Terminal()
|
||||
if err == nil {
|
||||
fmt.Println(ss)
|
||||
}
|
||||
|
||||
fmt.Printf("Name: %s PId: %v ParentID: %v Username: %v status:%s startTime: %s numThreads: %v numConnections:%v cpuPercent:%v rss:%s MB IORead: %s IOWrite: %s \n",
|
||||
name, pro.Pid, parentID, userName, status, startTime, numThreads, numConnections, cpuPercent, rss, ioRead, ioWrite)
|
||||
}
|
||||
users, err := host.Users()
|
||||
if err == nil {
|
||||
fmt.Println(users)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
package ssl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-acme/lego/v4/acme/api"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
|
||||
"log"
|
||||
)
|
||||
|
||||
type AppList struct {
|
||||
Version string `json:"version"`
|
||||
Tags []Tag `json:"tags"`
|
||||
Items []AppDefine `json:"items"`
|
||||
}
|
||||
|
||||
type NewAppDefine struct {
|
||||
Name string `yaml:"name"`
|
||||
Tags []string `yaml:"tags"`
|
||||
Title string `yaml:"title"`
|
||||
Type string `yaml:"type"`
|
||||
Description string `yaml:"description"`
|
||||
AdditionalProperties AppDefine `yaml:"additionalProperties"`
|
||||
}
|
||||
|
||||
type NewAppConfig struct {
|
||||
AdditionalProperties map[string]interface{} `yaml:"additionalProperties"`
|
||||
}
|
||||
|
||||
type AppDefine struct {
|
||||
Key string `json:"key" yaml:"key"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Tags []string `json:"tags" yaml:"tags"`
|
||||
Versions []string `json:"versions" yaml:"-"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
|
||||
Limit int `json:"limit" yaml:"limit"`
|
||||
Recommend int `json:"recommend" yaml:"recommend"`
|
||||
Website string `json:"website" yaml:"website"`
|
||||
Github string `json:"github" yaml:"github"`
|
||||
Document string `json:"document" yaml:"document"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Key string `json:"key" yaml:"key"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
}
|
||||
|
||||
func getTagName(key string, tags []Tag) string {
|
||||
result := "应用"
|
||||
for _, tag := range tags {
|
||||
if tag.Key == key {
|
||||
return tag.Name
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func TestAppToV2(t *testing.T) {
|
||||
oldDir := "/Users/wangzhengkun/projects/github.com/1Panel-dev/appstore/apps"
|
||||
newDir := "/Users/wangzhengkun/projects/github.com/1Panel-dev/appstore/apps_new"
|
||||
listJsonDir := path.Join(oldDir, "list.json")
|
||||
fileOp := files.NewFileOp()
|
||||
content, err := fileOp.GetContent(listJsonDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
appList := &AppList{}
|
||||
if err = json.Unmarshal(content, appList); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, appDefine := range appList.Items {
|
||||
newAppDefine := &NewAppDefine{
|
||||
Name: appDefine.Name,
|
||||
Tags: []string{getTagName(appDefine.Tags[0], appList.Tags)},
|
||||
Type: getTagName(appDefine.Tags[0], appList.Tags),
|
||||
Title: appDefine.ShortDescZh,
|
||||
Description: appDefine.ShortDescZh,
|
||||
AdditionalProperties: appDefine,
|
||||
}
|
||||
|
||||
yamlContent, err := yaml.Marshal(newAppDefine)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
oldAppDir := oldDir + "/" + appDefine.Key
|
||||
newAppDir := newDir + "/" + appDefine.Key
|
||||
if !fileOp.Stat(newAppDir) {
|
||||
if err := fileOp.CreateDir(newAppDir, 0755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// logo
|
||||
oldLogoPath := oldAppDir + "/metadata/logo.png"
|
||||
if err := fileOp.CopyFile(oldLogoPath, newAppDir); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, version := range appDefine.Versions {
|
||||
oldVersionDir := oldAppDir + "/versions/" + version
|
||||
if err := fileOp.CopyDir(oldVersionDir, newAppDir); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
oldConfigPath := oldVersionDir + "/config.json"
|
||||
configContent, err := fileOp.GetContent(oldConfigPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(configContent, &result); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newConfigD := &NewAppConfig{}
|
||||
newConfigD.AdditionalProperties = result
|
||||
configYamlByte, err := yaml.Marshal(newConfigD)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newVersionDir := newAppDir + "/" + version
|
||||
if err := fileOp.WriteFile(newVersionDir+"/data.yml", bytes.NewReader(configYamlByte), 0755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := fileOp.WriteFile(newAppDir+"/data.yml", bytes.NewReader(yamlContent), 0755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = fileOp.DeleteFile(newVersionDir + "/config.json")
|
||||
oldReadMefile := newVersionDir + "/README.md"
|
||||
_ = fileOp.Cut([]string{oldReadMefile}, newAppDir, "", false)
|
||||
_ = fileOp.DeleteFile(oldReadMefile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePrivate(t *testing.T) {
|
||||
priKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
derStream := x509.MarshalPKCS1PrivateKey(priKey)
|
||||
block := &pem.Block{
|
||||
Type: "privateKey",
|
||||
Bytes: derStream,
|
||||
}
|
||||
file, err := os.Create("private.key")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = pem.Encode(file, block); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSL(t *testing.T) {
|
||||
|
||||
priKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate private key: %v", err)
|
||||
}
|
||||
|
||||
myUser := AcmeUser{
|
||||
Email: "you2@yours.com",
|
||||
Key: priKey,
|
||||
}
|
||||
|
||||
config := lego.NewConfig(&myUser)
|
||||
//config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
config.CADirURL = "https://acme.zerossl.com/v2/DV90"
|
||||
config.UserAgent = "acm_go/0.0.1"
|
||||
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
//config.HTTPClient = httpClient
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
myUser.Registration = reg
|
||||
|
||||
//获取证书
|
||||
//certificates, err := client.Certificate.Get("https://acme-v02.api.letsencrypt.org/acme/cert/049cb98a8b3ea5a73f08dcdcf89263af8323", true)
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//certificates, err = client.Certificate.Renew(*certificates, true, true, "")
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
|
||||
//申请证书
|
||||
ewDomain := "tuxpanel.com"
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{ewDomain},
|
||||
// 证书链
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(&manualDnsProvider{}, dns01.AddDNSTimeout(6*time.Minute))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
core, err := api.New(config.HTTPClient, config.UserAgent, config.CADirURL, reg.URI, priKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
order, err := core.Orders.New([]string{ewDomain})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
auth, err := core.Authorizations.Get(order.Authorizations[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
domain := challenge.GetTargetedDomain(auth)
|
||||
chlng, err := challenge.FindChallenge(challenge.DNS01, auth)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyAuth, err := core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
fmt.Println("fqdn", fqdn, value)
|
||||
|
||||
//
|
||||
//keyAuth, err := client.Challenge.core.GetKeyAuthorization(chlng.Token)
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//
|
||||
|
||||
//httpProvider, err := webroot.NewHTTPProvider("/opt/1Panel/data/apps/nginx/nginx-1/www/wwwroot")
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//
|
||||
//err = client.Challenge.SetHTTP01Provider(httpProvider)
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("43.142.178.16", "443"))
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("---private---")
|
||||
fmt.Println(string(certificates.PrivateKey))
|
||||
fmt.Println("---.pem---")
|
||||
fmt.Println(string(certificates.Certificate))
|
||||
fmt.Println("---.domain---")
|
||||
fmt.Println(certificates.Domain)
|
||||
fmt.Println("---.certUrl---")
|
||||
fmt.Println(certificates.CertURL)
|
||||
fmt.Println("---.csr---")
|
||||
fmt.Println(certificates.CSR)
|
||||
fmt.Println("---.cert string---")
|
||||
fmt.Println(string(certificates.IssuerCertificate))
|
||||
|
||||
cer1, _ := pem.Decode(certificates.Certificate)
|
||||
|
||||
cert, err := x509.ParseCertificate(cer1.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(cert)
|
||||
|
||||
cer2, _ := pem.Decode(certificates.IssuerCertificate)
|
||||
|
||||
cert2, err := x509.ParseCertificate(cer2.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(cert2)
|
||||
|
||||
}
|
||||
|
||||
func generateCSR(privateKey crypto.PrivateKey, domain string) ([]byte, error) {
|
||||
// 创建证书请求的模板
|
||||
template := x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: domain,
|
||||
},
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
}
|
||||
|
||||
// 生成 CSR
|
||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将 CSR 编码为 PEM 格式
|
||||
csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
|
||||
|
||||
// 这里可以将 CSR 写入文件或者返回
|
||||
err = os.WriteFile("csr.pem", csrPEM, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return csrPEM, nil
|
||||
}
|
||||
|
||||
func TestZeroSSL(t *testing.T) {
|
||||
|
||||
domain := "1panel.store"
|
||||
acmeServer := "https://acme.zerossl.com/v2/DV90"
|
||||
|
||||
//acmeServer = "https://api.test4.buypass.no/acme/directory"
|
||||
//
|
||||
//acmeServer = "https://api.buypass.com/acme/directory"
|
||||
|
||||
priKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate private key: %v", err)
|
||||
}
|
||||
|
||||
user := AcmeUser{
|
||||
Email: "zhengkunwang123@sina.com",
|
||||
Key: priKey,
|
||||
}
|
||||
|
||||
//logFile, err := os.OpenFile("/opt/1panel/ssl.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
//if err != nil {
|
||||
// log.Fatalf("Failed to open log file: %v", err)
|
||||
//}
|
||||
//defer logFile.Close()
|
||||
//
|
||||
//logger := log.New(logFile, "", log.LstdFlags)
|
||||
//legoLogger.Logger = logger
|
||||
|
||||
config := lego.NewConfig(&user)
|
||||
|
||||
// 设置ACME服务器URL
|
||||
config.CADirURL = acmeServer
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
config.UserAgent = "acm_go/0.0.1"
|
||||
|
||||
// 创建ACME客户端
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// ZeroSSl
|
||||
|
||||
kid := ""
|
||||
hmacEncoded := ""
|
||||
|
||||
eabOptions := registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
Kid: kid,
|
||||
HmacEncoded: hmacEncoded,
|
||||
}
|
||||
|
||||
reg, err := client.Registration.RegisterWithExternalAccountBinding(eabOptions)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// ZeroSSl
|
||||
|
||||
//reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
//if err != nil {
|
||||
// log.Fatal(err)
|
||||
//}
|
||||
|
||||
user.Registration = reg
|
||||
|
||||
cloudflareConfig := cloudflare.NewDefaultConfig()
|
||||
cloudflareConfig.AuthEmail = ""
|
||||
cloudflareConfig.AuthKey = ""
|
||||
p, err := cloudflare.NewDNSProviderConfig(cloudflareConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 申请证书
|
||||
pk, err := certcrypto.GeneratePrivateKey(certcrypto.EC256)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{domain},
|
||||
Bundle: true,
|
||||
PrivateKey: pk,
|
||||
}
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 保存证书
|
||||
err = os.WriteFile("certificate.crt", certificates.Certificate, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile("private.key", certificates.PrivateKey, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEABCre(t *testing.T) {
|
||||
res, err := getZeroSSLEabCredentials("zen@11.com")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("%v", res)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user