Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3e6cd1cab1 | ||
![]() |
98df3806f5 | ||
![]() |
18e8af6234 | ||
![]() |
5bbda8f842 | ||
![]() |
85f8c1e634 | ||
![]() |
85c935ee46 | ||
![]() |
b4033471e7 | ||
![]() |
8dca519068 | ||
![]() |
39d8b0d98c | ||
![]() |
278a562320 | ||
![]() |
c0d8578466 | ||
![]() |
b8480e4928 | ||
![]() |
1ff39d7b85 | ||
![]() |
b1f817f09b | ||
![]() |
018fb85bc3 | ||
![]() |
bb7102d543 | ||
![]() |
1cccfeff22 | ||
![]() |
6c3a5b0ec7 | ||
![]() |
3fffb3c8ec | ||
![]() |
a51d2d15de | ||
![]() |
1c0d0d46a7 | ||
![]() |
65b8d47310 | ||
![]() |
ca586bb766 | ||
![]() |
25ccadea9e | ||
![]() |
87e9662af4 | ||
![]() |
fe705a25ea | ||
![]() |
7e5cdbf953 | ||
![]() |
e262f4fa40 | ||
![]() |
f4d5b5437e | ||
![]() |
ce258cf157 | ||
![]() |
381233a8a5 | ||
![]() |
0e8a4eaf2e | ||
![]() |
0c4400d6f7 | ||
![]() |
075ae253a1 | ||
![]() |
fc57256197 | ||
![]() |
e0f15ca783 | ||
![]() |
1931e8800a | ||
![]() |
fb4a5491eb | ||
![]() |
cd77c672bc | ||
![]() |
9b02e88e3c | ||
![]() |
2e73857b42 | ||
![]() |
7721395748 | ||
![]() |
c326606401 | ||
![]() |
c9ea6f6c8d | ||
![]() |
8c2d3e432d | ||
![]() |
20a47afb8c | ||
![]() |
07d5c580a6 | ||
![]() |
480b8acd66 | ||
![]() |
ac7e47bdf1 | ||
![]() |
a516ba9d63 | ||
![]() |
5f357db1e1 | ||
![]() |
5944f67823 | ||
![]() |
aa1e548ddf | ||
![]() |
80e845f320 | ||
![]() |
d4e6232664 | ||
![]() |
43e1ec0244 | ||
![]() |
e30546102e | ||
![]() |
70319aca45 | ||
![]() |
7c6f8ff7c4 | ||
![]() |
7eb96a35df | ||
![]() |
e52e6ddaa2 | ||
![]() |
40d3392520 | ||
![]() |
b83147220a | ||
![]() |
a031d3ba41 | ||
![]() |
df770460d6 | ||
![]() |
0f6e14d2f9 | ||
![]() |
b0320a4ebc | ||
![]() |
e4462c06fe | ||
![]() |
c7e7629d85 | ||
![]() |
d34e7492e1 | ||
![]() |
f6b84d384e | ||
![]() |
202a2ad7ae | ||
![]() |
6f6c836d9a | ||
![]() |
01d5bd047f | ||
![]() |
f000f8b46c | ||
![]() |
ae8b6b8c05 | ||
![]() |
b7d09d0201 | ||
![]() |
67bba4bc1d | ||
![]() |
a0a26f237b | ||
![]() |
a84d8dd828 | ||
![]() |
39abd4341d | ||
![]() |
b31de5e637 | ||
![]() |
09653f9c06 | ||
![]() |
e0ca9507de | ||
![]() |
b59ccc52ae | ||
![]() |
f9c8aa0484 | ||
![]() |
5629dbff8e | ||
![]() |
ed0923fd28 | ||
![]() |
489efb7ac1 | ||
![]() |
6a0b4a1176 | ||
![]() |
7a67377aa9 | ||
![]() |
40aaa1ceb0 | ||
![]() |
e83e592e0a | ||
![]() |
87a7cf3aca | ||
![]() |
2a20ff0b79 | ||
![]() |
1a6c45c526 | ||
![]() |
4d368a4de8 | ||
![]() |
b05e5736a6 | ||
![]() |
3b9f92e03f | ||
![]() |
da4c264908 | ||
![]() |
bbd649188a | ||
![]() |
cd742b0933 | ||
![]() |
7f79f5f031 | ||
![]() |
cb7351a9fb | ||
![]() |
bd5dc56b66 | ||
![]() |
34e8d88a53 | ||
![]() |
c14e192bee | ||
![]() |
d31eacd049 | ||
![]() |
f556038425 | ||
![]() |
03ed4c8071 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -33,3 +33,5 @@ install.sh
|
||||
quick_start.sh
|
||||
cmd/server/web/.DS_Store
|
||||
cmd/server/.DS_Store
|
||||
cmd/server/fileList.txt
|
||||
.fileList.txt
|
||||
|
4
Makefile
4
Makefile
@@ -29,10 +29,6 @@ build_backend_on_darwin:
|
||||
cd $(SERVER_PATH) \
|
||||
&& GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_backend_on_archlinux:
|
||||
cd $(SERVER_PATH) \
|
||||
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_all: build_frontend build_backend_on_linux
|
||||
|
||||
build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin
|
||||
|
@@ -120,6 +120,20 @@ func (b *BaseApi) CheckIsDemo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, global.CONF.System.IsDemo)
|
||||
}
|
||||
|
||||
// @Tags Auth
|
||||
// @Summary Load System Language
|
||||
// @Description 获取系统语言设置
|
||||
// @Success 200
|
||||
// @Router /auth/language [get]
|
||||
func (b *BaseApi) GetLanguage(c *gin.Context) {
|
||||
settingInfo, err := settingService.GetSettingInfo()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, settingInfo.Language)
|
||||
}
|
||||
|
||||
func saveLoginLogs(c *gin.Context, err error) {
|
||||
var logs model.LoginLog
|
||||
if err != nil {
|
||||
|
@@ -355,6 +355,24 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Load container log
|
||||
// @Description 获取容器操作日志
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithNameAndType true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /containers/load/log [post]
|
||||
func (b *BaseApi) LoadContainerLog(c *gin.Context) {
|
||||
var req dto.OperationWithNameAndType
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
content := containerService.LoadContainerLogs(req)
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Operate Container
|
||||
// @Description 容器操作
|
||||
|
@@ -94,6 +94,28 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Cronjob
|
||||
// @Summary Load Cronjob record log
|
||||
// @Description 获取计划任务记录日志
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /cronjob/record/log [post]
|
||||
func (b *BaseApi) LoadRecordLog(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
content, err := cronjobService.LoadRecordLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
// @Tags Cronjob
|
||||
// @Summary Clean job records
|
||||
// @Description 清空计划任务记录
|
||||
@@ -224,7 +246,8 @@ func (b *BaseApi) TargetDownload(c *gin.Context) {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, filePath)
|
||||
|
||||
c.File(filePath)
|
||||
}
|
||||
|
||||
// @Tags Cronjob
|
||||
|
@@ -188,12 +188,12 @@ func (b *BaseApi) UpdateMysqlConfByFile(c *gin.Context) {
|
||||
// @Summary Page mysql databases
|
||||
// @Description 获取 mysql 数据库列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Param request body dto.MysqlDBSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/search [post]
|
||||
func (b *BaseApi) SearchMysql(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
var req dto.MysqlDBSearch
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
@@ -216,11 +216,11 @@ func (b *BaseApi) SearchMysql(c *gin.Context) {
|
||||
// @Description 获取 mysql 数据库列表
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Success 200 {array} string
|
||||
// @Success 200 {array} dto.MysqlOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/options [get]
|
||||
func (b *BaseApi) ListDBName(c *gin.Context) {
|
||||
list, err := mysqlService.ListDBName()
|
||||
list, err := mysqlService.ListDBOption()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@@ -229,6 +229,25 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Load mysql database from remote
|
||||
// @Description 从服务器获取
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/load/:from [get]
|
||||
func (b *BaseApi) LoadDBFromRemote(c *gin.Context) {
|
||||
from, err := helper.GetStrParamByKey(c, "from")
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := mysqlService.LoadFromRemote(from); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Check before delete mysql database
|
||||
// @Description Mysql 数据库删除前检查
|
||||
@@ -302,6 +321,28 @@ func (b *BaseApi) LoadBaseinfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Load Database file
|
||||
// @Description 获取数据库文件
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithNameAndType true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/load/file [post]
|
||||
func (b *BaseApi) LoadDatabaseFile(c *gin.Context) {
|
||||
var req dto.OperationWithNameAndType
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
content, err := mysqlService.LoadDatabaseFile(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
// @Tags Database Mysql
|
||||
// @Summary Load mysql remote access
|
||||
// @Description 获取 mysql 远程访问权限
|
||||
|
@@ -21,8 +21,9 @@ var (
|
||||
imageService = service.NewIImageService()
|
||||
dockerService = service.NewIDockerService()
|
||||
|
||||
mysqlService = service.NewIMysqlService()
|
||||
redisService = service.NewIRedisService()
|
||||
mysqlService = service.NewIMysqlService()
|
||||
remoteDBService = service.NewIRemoteDBService()
|
||||
redisService = service.NewIRedisService()
|
||||
|
||||
cronjobService = service.NewICronjobService()
|
||||
|
||||
@@ -50,4 +51,6 @@ var (
|
||||
|
||||
runtimeService = service.NewRuntimeService()
|
||||
processService = service.NewIProcessService()
|
||||
|
||||
hostToolService = service.NewIHostToolService()
|
||||
)
|
||||
|
@@ -544,28 +544,6 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Download file with path
|
||||
// @Description 下载指定文件
|
||||
// @Accept json
|
||||
// @Param request body dto.FilePath true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/download/bypath [post]
|
||||
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [path]","formatEN":"Download file [path]"}
|
||||
func (b *BaseApi) DownloadFile(c *gin.Context) {
|
||||
var req dto.FilePath
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
c.File(req.Path)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Load file size
|
||||
// @Description 获取文件夹大小
|
||||
@@ -589,33 +567,6 @@ func (b *BaseApi) Size(c *gin.Context) {
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Read file
|
||||
// @Description 读取文件
|
||||
// @Accept json
|
||||
// @Param request body dto.FilePath true "request"
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /files/loadfile [post]
|
||||
func (b *BaseApi) LoadFromFile(c *gin.Context) {
|
||||
var req dto.FilePath
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, string(content))
|
||||
}
|
||||
|
||||
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
|
||||
if _, err := os.Stat(path.Dir(dstDir)); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(dstDir), os.ModePerm); err != nil {
|
||||
|
213
backend/app/api/v1/host_tool.go
Normal file
213
backend/app/api/v1/host_tool.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool
|
||||
// @Description 获取主机工具状态
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool [post]
|
||||
func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := hostToolService.GetToolStatus(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, config)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Create Host tool Config
|
||||
// @Description 创建主机工具配置
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/create [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
|
||||
func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
var req request.HostToolCreate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := hostToolService.CreateToolConfig(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Operate tool
|
||||
// @Description 操作主机工具
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
|
||||
func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := hostToolService.OperateTool(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool config
|
||||
// @Description 操作主机工具配置文件
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolConfig true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/config [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
|
||||
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
var req request.HostToolConfig
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := hostToolService.OperateToolConfig(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, config)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get tool
|
||||
// @Description 获取主机工具日志
|
||||
// @Accept json
|
||||
// @Param request body request.HostToolLogReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/log [post]
|
||||
func (b *BaseApi) GetToolLog(c *gin.Context) {
|
||||
var req request.HostToolLogReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
logContent, err := hostToolService.GetToolLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, logContent)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Create Supervisor process
|
||||
// @Description 操作守护进程
|
||||
// @Accept json
|
||||
// @Param request body request.SupervisorProcessConfig true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/supervisor/process [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
|
||||
func (b *BaseApi) OperateProcess(c *gin.Context) {
|
||||
var req request.SupervisorProcessConfig
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := hostToolService.OperateSupervisorProcess(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get Supervisor process config
|
||||
// @Description 获取 Supervisor 进程配置
|
||||
// @Accept json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/supervisor/process [get]
|
||||
func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
configs, err := hostToolService.GetSupervisorProcessConfig()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, configs)
|
||||
}
|
||||
|
||||
// @Tags Host tool
|
||||
// @Summary Get Supervisor process config
|
||||
// @Description 操作 Supervisor 进程文件
|
||||
// @Accept json
|
||||
// @Param request body request.SupervisorProcessFileReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/tool/supervisor/process/file [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
|
||||
func (b *BaseApi) GetProcessFile(c *gin.Context) {
|
||||
var req request.SupervisorProcessFileReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
content, err := hostToolService.OperateSupervisorProcessFile(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
@@ -89,3 +89,19 @@ func (b *BaseApi) CleanLogs(c *gin.Context) {
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Logs
|
||||
// @Summary Load system logs
|
||||
// @Description 获取系统日志
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /logs/system [get]
|
||||
func (b *BaseApi) GetSystemLogs(c *gin.Context) {
|
||||
data, err := logService.LoadSystemLog()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
180
backend/app/api/v1/remote_db.go
Normal file
180
backend/app/api/v1/remote_db.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Create remote database
|
||||
// @Description 创建远程数据库
|
||||
// @Accept json
|
||||
// @Param request body dto.RemoteDBCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote [post]
|
||||
// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建远程数据库 [name][type]","formatEN":"create remote database [name][type]"}
|
||||
func (b *BaseApi) CreateRemoteDB(c *gin.Context) {
|
||||
var req dto.RemoteDBCreate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := remoteDBService.Create(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Check remote database
|
||||
// @Description 检测远程数据库连接性
|
||||
// @Accept json
|
||||
// @Param request body dto.RemoteDBCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote/check [post]
|
||||
// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"检测远程数据库 [name][type] 连接性","formatEN":"check if remote database [name][type] is connectable"}
|
||||
func (b *BaseApi) CheckeRemoteDB(c *gin.Context) {
|
||||
var req dto.RemoteDBCreate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, remoteDBService.CheckeRemoteDB(req))
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Page remote databases
|
||||
// @Description 获取远程数据库列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.RemoteDBSearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote/search [post]
|
||||
func (b *BaseApi) SearchRemoteDB(c *gin.Context) {
|
||||
var req dto.RemoteDBSearch
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := remoteDBService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary List remote databases
|
||||
// @Description 获取远程数据库列表
|
||||
// @Success 200 {array} dto.RemoteDBOption
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote/list/:type [get]
|
||||
func (b *BaseApi) ListRemoteDB(c *gin.Context) {
|
||||
dbType, err := helper.GetStrParamByKey(c, "type")
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
list, err := remoteDBService.List(dbType)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Get remote databases
|
||||
// @Description 获取远程数据库
|
||||
// @Success 200 {object} dto.RemoteDBInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote/:name [get]
|
||||
func (b *BaseApi) GetRemoteDB(c *gin.Context) {
|
||||
name, err := helper.GetStrParamByKey(c, "name")
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
data, err := remoteDBService.Get(name)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Delete remote database
|
||||
// @Description 删除远程数据库
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"databases","output_column":"name","output_value":"names"}],"formatZH":"删除远程数据库 [names]","formatEN":"delete remote database [names]"}
|
||||
func (b *BaseApi) DeleteRemoteDB(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := remoteDBService.Delete(req.ID); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Update remote database
|
||||
// @Description 更新远程数据库
|
||||
// @Accept json
|
||||
// @Param request body dto.RemoteDBUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote/update [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新远程数据库 [name]","formatEN":"update remote database [name]"}
|
||||
func (b *BaseApi) UpdateRemoteDB(c *gin.Context) {
|
||||
var req dto.RemoteDBUpdate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := remoteDBService.Update(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
@@ -3,6 +3,8 @@ package v1
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
@@ -134,6 +136,22 @@ func (b *BaseApi) LoadFromCert(c *gin.Context) {
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Download system cert
|
||||
// @Description 下载证书
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/ssl/download [post]
|
||||
func (b *BaseApi) DownloadSSL(c *gin.Context) {
|
||||
pathItem := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
|
||||
if _, err := os.Stat(pathItem); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.File(pathItem)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Update system port
|
||||
// @Description 更新系统端口
|
||||
|
@@ -183,3 +183,18 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Load host ssh conf
|
||||
// @Description 获取 ssh 配置文件
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /host/ssh/conf [get]
|
||||
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
|
||||
data, err := sshService.LoadSSHConf()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
@@ -55,12 +55,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
ssConn, err := connInfo.NewSshConn(cols, rows)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer ssConn.Close()
|
||||
|
||||
|
||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, connInfo.Client, wsConn)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
|
@@ -428,6 +428,28 @@ func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
|
||||
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":[],"BeforeFuntions":[{"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 := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
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 配置
|
||||
@@ -561,6 +583,28 @@ func (b *BaseApi) UpdatePHPFile(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Website PHP
|
||||
// @Summary Update php version
|
||||
// @Description 变更 php 版本
|
||||
// @Accept json
|
||||
// @Param request body request.WebsitePHPVersionReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/php/version [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 版本变更 [domain]","formatEN":"php version update [domain]"}
|
||||
func (b *BaseApi) ChangePHPVersion(c *gin.Context) {
|
||||
var req request.WebsitePHPVersionReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := websiteService.ChangePHPVersion(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Get rewrite conf
|
||||
// @Description 获取伪静态配置
|
||||
@@ -801,3 +845,70 @@ func (b *BaseApi) UpdateAntiLeech(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Update redirect conf
|
||||
// @Description 修改重定向配置
|
||||
// @Accept json
|
||||
// @Param request body request.NginxRedirectReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/redirect/update [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 重定向理配置 ","formatEN":"Update domain [domain] redirect config"}
|
||||
func (b *BaseApi) UpdateRedirectConfig(c *gin.Context) {
|
||||
var req request.NginxRedirectReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
err := websiteService.OperateRedirect(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Get redirect conf
|
||||
// @Description 获取重定向配置
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteProxyReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/redirect [post]
|
||||
func (b *BaseApi) GetRedirectConfig(c *gin.Context) {
|
||||
var req request.WebsiteRedirectReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
res, err := websiteService.GetRedirect(req.WebsiteID)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Update redirect file
|
||||
// @Description 更新重定向文件
|
||||
// @Accept json
|
||||
// @Param request body request.NginxRedirectUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/redirect/file [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新重定向文件 [domain]","formatEN":"Nginx conf redirect file update [domain]"}
|
||||
func (b *BaseApi) UpdateRedirectConfigFile(c *gin.Context) {
|
||||
var req request.NginxRedirectUpdate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := websiteService.UpdateRedirectFile(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
@@ -15,6 +15,10 @@ type AuthParam struct {
|
||||
RootPassword string `json:"PANEL_DB_ROOT_PASSWORD"`
|
||||
}
|
||||
|
||||
type RedisAuthParam struct {
|
||||
RootPassword string `json:"PANEL_REDIS_ROOT_PASSWORD"`
|
||||
}
|
||||
|
||||
type ContainerExec struct {
|
||||
ContainerName string `json:"containerName"`
|
||||
DbParam AppDatabase `json:"dbParam"`
|
||||
|
@@ -24,6 +24,7 @@ type Login struct {
|
||||
Captcha string `json:"captcha"`
|
||||
CaptchaID string `json:"captchaID"`
|
||||
AuthMethod string `json:"authMethod"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
type MFALogin struct {
|
||||
|
@@ -2,10 +2,20 @@ package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type MysqlDBSearch struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
From string `json:"from"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
|
||||
type MysqlDBInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Name string `json:"name"`
|
||||
From string `json:"from"`
|
||||
MysqlName string `json:"mysqlName"`
|
||||
Format string `json:"format"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
@@ -14,8 +24,15 @@ type MysqlDBInfo struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type MysqlOption struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
From string `json:"from"`
|
||||
}
|
||||
|
||||
type MysqlDBCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
From string `json:"from" validate:"required"`
|
||||
Format string `json:"format" validate:"required,oneof=utf8mb4 utf8 gbk big5"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
@@ -100,6 +117,7 @@ type MysqlConfUpdateByFile struct {
|
||||
|
||||
type ChangeDBInfo struct {
|
||||
ID uint `json:"id"`
|
||||
From string `json:"from"`
|
||||
Value string `json:"value" validate:"required"`
|
||||
}
|
||||
|
||||
|
@@ -6,8 +6,8 @@ type ImageRepoCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username" validate:"max=256"`
|
||||
Password string `json:"password" validate:"max=256"`
|
||||
Auth bool `json:"auth"`
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ type ImageRepoUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username" validate:"max=256"`
|
||||
Password string `json:"password" validate:"max=256"`
|
||||
Auth bool `json:"auth"`
|
||||
}
|
||||
|
||||
|
52
backend/app/dto/remote_db.go
Normal file
52
backend/app/dto/remote_db.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type RemoteDBSearch struct {
|
||||
PageInfo
|
||||
Info string `json:"info"`
|
||||
Type string `json:"type"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
|
||||
type RemoteDBInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Name string `json:"name" validate:"max=256"`
|
||||
From string `json:"from"`
|
||||
Version string `json:"version"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type RemoteDBOption struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type RemoteDBCreate struct {
|
||||
Name string `json:"name" validate:"required,max=256"`
|
||||
Type string `json:"type" validate:"required,oneof=mysql"`
|
||||
From string `json:"from" validate:"required,oneof=local remote"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type RemoteDBUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
@@ -58,6 +58,7 @@ type AppInstalledOperate struct {
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
DeleteBackup bool `json:"deleteBackup"`
|
||||
DeleteDB bool `json:"deleteDB"`
|
||||
Backup bool `json:"backup"`
|
||||
}
|
||||
|
||||
type AppInstalledUpdate struct {
|
||||
|
41
backend/app/dto/request/host_tool.go
Normal file
41
backend/app/dto/request/host_tool.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package request
|
||||
|
||||
type HostToolReq struct {
|
||||
Type string `json:"type" validate:"required,oneof=supervisord"`
|
||||
Operate string `json:"operate" validate:"oneof=status restart start stop"`
|
||||
}
|
||||
|
||||
type HostToolCreate struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
SupervisorConfig
|
||||
}
|
||||
|
||||
type SupervisorConfig struct {
|
||||
ConfigPath string `json:"configPath"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
}
|
||||
|
||||
type HostToolLogReq struct {
|
||||
Type string `json:"type" validate:"required,oneof=supervisord"`
|
||||
}
|
||||
|
||||
type HostToolConfig struct {
|
||||
Type string `json:"type" validate:"required,oneof=supervisord"`
|
||||
Operate string `json:"operate" validate:"oneof=get set"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type SupervisorProcessConfig struct {
|
||||
Name string `json:"name"`
|
||||
Operate string `json:"operate"`
|
||||
Command string `json:"command"`
|
||||
User string `json:"user"`
|
||||
Dir string `json:"dir"`
|
||||
Numprocs string `json:"numprocs"`
|
||||
}
|
||||
type SupervisorProcessFileReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required,oneof=get clear update" `
|
||||
Content string `json:"content"`
|
||||
File string `json:"file" validate:"required,oneof=out.log err.log config"`
|
||||
}
|
@@ -3,9 +3,8 @@ package request
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
type NginxConfigFileUpdate struct {
|
||||
Content string `json:"content" validate:"required"`
|
||||
FilePath string `json:"filePath" validate:"required"`
|
||||
Backup bool `json:"backup" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Backup bool `json:"backup" validate:"required"`
|
||||
}
|
||||
|
||||
type NginxScopeReq struct {
|
||||
@@ -66,3 +65,23 @@ type NginxAntiLeechUpdate struct {
|
||||
LogEnable bool `json:"logEnable"`
|
||||
Blocked bool `json:"blocked"`
|
||||
}
|
||||
|
||||
type NginxRedirectReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
Domains []string `json:"domains"`
|
||||
KeepPath bool `json:"keepPath" validate:"required"`
|
||||
Enable bool `json:"enable" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Redirect string `json:"redirect" validate:"required"`
|
||||
Path string `json:"path"`
|
||||
Target string `json:"target" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
RedirectRoot bool `json:"redirectRoot"`
|
||||
}
|
||||
|
||||
type NginxRedirectUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
@@ -75,6 +75,12 @@ type WebsiteWafReq struct {
|
||||
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" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteWafUpdate struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
@@ -158,6 +164,12 @@ type WebsitePHPFileUpdate struct {
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsitePHPVersionReq struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
RuntimeID uint `json:"runtimeID" validate:"required"`
|
||||
RetainConfig bool `json:"retainConfig" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteUpdateDir struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
SiteDir string `json:"siteDir" validate:"required"`
|
||||
@@ -189,3 +201,13 @@ type WebsiteProxyConfig struct {
|
||||
type WebsiteProxyReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
41
backend/app/dto/response/host_tool.go
Normal file
41
backend/app/dto/response/host_tool.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package response
|
||||
|
||||
type HostToolRes struct {
|
||||
Type string `json:"type"`
|
||||
Config interface{} `json:"config"`
|
||||
}
|
||||
|
||||
type Supervisor struct {
|
||||
ConfigPath string `json:"configPath"`
|
||||
IncludeDir string `json:"includeDir"`
|
||||
LogPath string `json:"logPath"`
|
||||
IsExist bool `json:"isExist"`
|
||||
Init bool `json:"init"`
|
||||
Msg string `json:"msg"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
CtlExist bool `json:"ctlExist"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
}
|
||||
|
||||
type HostToolConfig struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type SupervisorProcessConfig struct {
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
User string `json:"user"`
|
||||
Dir string `json:"dir"`
|
||||
Numprocs string `json:"numprocs"`
|
||||
Msg string `json:"msg"`
|
||||
Status []ProcessStatus `json:"status"`
|
||||
}
|
||||
|
||||
type ProcessStatus struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
PID string `json:"PID"`
|
||||
Uptime string `json:"uptime"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
@@ -34,3 +34,22 @@ type NginxAntiLeechRes struct {
|
||||
LogEnable bool `json:"logEnable"`
|
||||
Blocked bool `json:"blocked"`
|
||||
}
|
||||
|
||||
type NginxRedirectConfig struct {
|
||||
WebsiteID uint `json:"websiteID"`
|
||||
Name string `json:"name"`
|
||||
Domains []string `json:"domains"`
|
||||
KeepPath bool `json:"keepPath"`
|
||||
Enable bool `json:"enable"`
|
||||
Type string `json:"type"`
|
||||
Redirect string `json:"redirect"`
|
||||
Path string `json:"path"`
|
||||
Target string `json:"target"`
|
||||
FilePath string `json:"filePath"`
|
||||
Content string `json:"content"`
|
||||
RedirectRoot bool `json:"redirectRoot"`
|
||||
}
|
||||
|
||||
type NginxFile struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
@@ -26,9 +26,8 @@ type WebsiteNginxConfig struct {
|
||||
}
|
||||
|
||||
type WebsiteWafConfig struct {
|
||||
Enable bool `json:"enable"`
|
||||
FilePath string `json:"filePath"`
|
||||
Content string `json:"content"`
|
||||
Enable bool `json:"enable"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type WebsiteHTTPS struct {
|
||||
|
@@ -3,6 +3,7 @@ package model
|
||||
type DatabaseMysql struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"type:varchar(256);not null"`
|
||||
From string `json:"from" gorm:"type:varchar(256);not null;default:local"`
|
||||
MysqlName string `json:"mysqlName" gorm:"type:varchar(64);not null"`
|
||||
Format string `json:"format" gorm:"type:varchar(64);not null"`
|
||||
Username string `json:"username" gorm:"type:varchar(256);not null"`
|
||||
|
14
backend/app/model/remote_db.go
Normal file
14
backend/app/model/remote_db.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
type RemoteDB struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"type:varchar(64);not null"`
|
||||
Type string `json:"type" gorm:"type:varchar(64);not null"`
|
||||
Version string `json:"version" gorm:"type:varchar(64);not null"`
|
||||
From string `json:"from" gorm:"type:varchar(64);not null"`
|
||||
Address string `json:"address" gorm:"type:varchar(64);not null"`
|
||||
Port uint `json:"port" gorm:"type:decimal;not null"`
|
||||
Username string `json:"username" gorm:"type:varchar(64)"`
|
||||
Password string `json:"password" gorm:"type:varchar(64)"`
|
||||
Description string `json:"description" gorm:"type:varchar(256);"`
|
||||
}
|
@@ -126,7 +126,7 @@ func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall)
|
||||
}
|
||||
|
||||
func (a *AppInstallRepo) Save(ctx context.Context, install *model.AppInstall) error {
|
||||
return getTx(ctx).Omit(clause.Associations).Save(&install).Error
|
||||
return getTx(ctx).Save(&install).Error
|
||||
}
|
||||
|
||||
func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error {
|
||||
@@ -192,10 +192,19 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
|
||||
if err := json.Unmarshal([]byte(appInstall.Env), &envMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
|
||||
if ok {
|
||||
info.Password = password
|
||||
switch app.Key {
|
||||
case "mysql":
|
||||
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
|
||||
if ok {
|
||||
info.Password = password
|
||||
}
|
||||
case "redis":
|
||||
password, ok := envMap["PANEL_REDIS_ROOT_PASSWORD"].(string)
|
||||
if ok {
|
||||
info.Password = password
|
||||
}
|
||||
}
|
||||
|
||||
userPassword, ok := envMap["PANEL_DB_USER_PASSWORD"].(string)
|
||||
if ok {
|
||||
info.UserPassword = userPassword
|
||||
|
@@ -13,13 +13,14 @@ type MysqlRepo struct{}
|
||||
type IMysqlRepo interface {
|
||||
Get(opts ...DBOption) (model.DatabaseMysql, error)
|
||||
WithByMysqlName(mysqlName string) DBOption
|
||||
WithByFrom(from string) DBOption
|
||||
List(opts ...DBOption) ([]model.DatabaseMysql, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.DatabaseMysql, error)
|
||||
Create(ctx context.Context, mysql *model.DatabaseMysql) error
|
||||
Delete(ctx context.Context, opts ...DBOption) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
UpdateDatabaseInfo(id uint, vars map[string]interface{}) error
|
||||
DeleteAll(ctx context.Context) error
|
||||
DeleteLocal(ctx context.Context) error
|
||||
}
|
||||
|
||||
func NewIMysqlRepo() IMysqlRepo {
|
||||
@@ -66,8 +67,8 @@ func (u *MysqlRepo) Delete(ctx context.Context, opts ...DBOption) error {
|
||||
return getTx(ctx, opts...).Delete(&model.DatabaseMysql{}).Error
|
||||
}
|
||||
|
||||
func (u *MysqlRepo) DeleteAll(ctx context.Context) error {
|
||||
return getTx(ctx).Where("1 = 1").Delete(&model.DatabaseMysql{}).Error
|
||||
func (u *MysqlRepo) DeleteLocal(ctx context.Context) error {
|
||||
return getTx(ctx).Where("`from` = ?", "local").Delete(&model.DatabaseMysql{}).Error
|
||||
}
|
||||
|
||||
func (u *MysqlRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
@@ -86,3 +87,12 @@ func (u *MysqlRepo) WithByMysqlName(mysqlName string) DBOption {
|
||||
return g.Where("mysql_name = ?", mysqlName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *MysqlRepo) WithByFrom(from string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(from) != 0 {
|
||||
return g.Where("`from` = ?", from)
|
||||
}
|
||||
return g
|
||||
}
|
||||
}
|
||||
|
84
backend/app/repo/remote_db.go
Normal file
84
backend/app/repo/remote_db.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type RemoteDBRepo struct{}
|
||||
|
||||
type IRemoteDBRepo interface {
|
||||
GetList(opts ...DBOption) ([]model.RemoteDB, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.RemoteDB, error)
|
||||
Create(database *model.RemoteDB) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
Get(opts ...DBOption) (model.RemoteDB, error)
|
||||
WithByFrom(from string) DBOption
|
||||
WithoutByFrom(from string) DBOption
|
||||
}
|
||||
|
||||
func NewIRemoteDBRepo() IRemoteDBRepo {
|
||||
return &RemoteDBRepo{}
|
||||
}
|
||||
|
||||
func (u *RemoteDBRepo) Get(opts ...DBOption) (model.RemoteDB, error) {
|
||||
var database model.RemoteDB
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&database).Error
|
||||
return database, err
|
||||
}
|
||||
|
||||
func (u *RemoteDBRepo) Page(page, size int, opts ...DBOption) (int64, []model.RemoteDB, error) {
|
||||
var users []model.RemoteDB
|
||||
db := global.DB.Model(&model.RemoteDB{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
|
||||
return count, users, err
|
||||
}
|
||||
|
||||
func (u *RemoteDBRepo) GetList(opts ...DBOption) ([]model.RemoteDB, error) {
|
||||
var databases []model.RemoteDB
|
||||
db := global.DB.Model(&model.RemoteDB{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&databases).Error
|
||||
return databases, err
|
||||
}
|
||||
|
||||
func (c *RemoteDBRepo) WithByFrom(from string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("`from` == ?", from)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RemoteDBRepo) WithoutByFrom(from string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("`from` != ?", from)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *RemoteDBRepo) Create(database *model.RemoteDB) error {
|
||||
return global.DB.Create(database).Error
|
||||
}
|
||||
|
||||
func (u *RemoteDBRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.RemoteDB{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (u *RemoteDBRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.RemoteDB{}).Error
|
||||
}
|
@@ -663,6 +663,7 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
|
||||
return nil, err
|
||||
}
|
||||
appStoreLastModified, _ := strconv.Atoi(setting.AppStoreLastModified)
|
||||
res.AppStoreLastModified = appStoreLastModified
|
||||
if setting.AppStoreLastModified == "" || lastModified != appStoreLastModified {
|
||||
res.CanUpdate = true
|
||||
return res, err
|
||||
@@ -687,13 +688,13 @@ func getAppFromRepo(downloadPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AppService) SyncAppListFromRemote() error {
|
||||
func (a AppService) SyncAppListFromRemote() (err error) {
|
||||
updateRes, err := a.GetAppUpdate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !updateRes.CanUpdate {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
if err = getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.System.AppRepo, global.CONF.System.Mode)); err != nil {
|
||||
return err
|
||||
@@ -704,10 +705,20 @@ func (a AppService) SyncAppListFromRemote() error {
|
||||
return err
|
||||
}
|
||||
list := &dto.AppList{}
|
||||
if err := json.Unmarshal(content, list); err != nil {
|
||||
if err = json.Unmarshal(content, list); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = NewISettingService().Update("AppStoreLastModified", strconv.Itoa(updateRes.AppStoreLastModified))
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
tags []*model.Tag
|
||||
appTags []*model.AppTag
|
||||
@@ -721,7 +732,7 @@ func (a AppService) SyncAppListFromRemote() error {
|
||||
}
|
||||
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
for _, old := range oldApps {
|
||||
oldAppIds = append(oldAppIds, old.ID)
|
||||
@@ -810,32 +821,32 @@ func (a AppService) SyncAppListFromRemote() error {
|
||||
tx, ctx := getTxAndContext()
|
||||
defer tx.Rollback()
|
||||
if len(addAppArray) > 0 {
|
||||
if err := appRepo.BatchCreate(ctx, addAppArray); err != nil {
|
||||
return err
|
||||
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(deleteAppArray) > 0 {
|
||||
if err := appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
|
||||
return err
|
||||
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
|
||||
return
|
||||
}
|
||||
if err := appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
|
||||
return err
|
||||
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := tagRepo.DeleteAll(ctx); err != nil {
|
||||
return err
|
||||
if err = tagRepo.DeleteAll(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
if err := tagRepo.BatchCreate(ctx, tags); err != nil {
|
||||
return err
|
||||
if err = tagRepo.BatchCreate(ctx, tags); err != nil {
|
||||
return
|
||||
}
|
||||
for _, t := range tags {
|
||||
tagMap[t.Key] = t.ID
|
||||
}
|
||||
}
|
||||
for _, update := range updateAppArray {
|
||||
if err := appRepo.Save(ctx, &update); err != nil {
|
||||
return err
|
||||
if err = appRepo.Save(ctx, &update); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
apps := append(addAppArray, updateAppArray...)
|
||||
@@ -879,35 +890,32 @@ func (a AppService) SyncAppListFromRemote() error {
|
||||
}
|
||||
}
|
||||
if len(addDetails) > 0 {
|
||||
if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
|
||||
return err
|
||||
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(deleteDetails) > 0 {
|
||||
if err := appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
|
||||
return err
|
||||
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, u := range updateDetails {
|
||||
if err := appDetailRepo.Update(ctx, u); err != nil {
|
||||
return err
|
||||
if err = appDetailRepo.Update(ctx, u); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(oldAppIds) > 0 {
|
||||
if err := appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
|
||||
return err
|
||||
if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(appTags) > 0 {
|
||||
if err := appTagRepo.BatchCreate(ctx, appTags); err != nil {
|
||||
return err
|
||||
if err = appTagRepo.BatchCreate(ctx, appTags); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
if err := NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
@@ -236,7 +236,7 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
case constant.Sync:
|
||||
return syncById(install.ID)
|
||||
case constant.Upgrade:
|
||||
return upgradeInstall(install.ID, req.DetailId)
|
||||
return upgradeInstall(install.ID, req.DetailId, req.Backup)
|
||||
default:
|
||||
return errors.New("operate not support")
|
||||
}
|
||||
@@ -569,10 +569,10 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(detail.Params), &appForm); err != nil {
|
||||
if err = json.Unmarshal([]byte(detail.Params), &appForm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, form := range appForm.FormFields {
|
||||
@@ -602,6 +602,17 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
appParam.Values = form.Values
|
||||
}
|
||||
params = append(params, appParam)
|
||||
} else {
|
||||
params = append(params, response.AppParam{
|
||||
Edit: form.Edit,
|
||||
Key: form.EnvKey,
|
||||
Rule: form.Rule,
|
||||
Type: form.Type,
|
||||
LabelZh: form.LabelZh,
|
||||
LabelEn: form.LabelEn,
|
||||
Value: form.Default,
|
||||
Values: form.Values,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +731,11 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
|
||||
envKey := ""
|
||||
switch param {
|
||||
case "password":
|
||||
envKey = "PANEL_DB_ROOT_PASSWORD="
|
||||
if appKey == "mysql" {
|
||||
envKey = "PANEL_DB_ROOT_PASSWORD="
|
||||
} else {
|
||||
envKey = "PANEL_REDIS_ROOT_PASSWORD="
|
||||
}
|
||||
case "port":
|
||||
envKey = "PANEL_APP_PORT_HTTP="
|
||||
case "user-password":
|
||||
@@ -749,6 +764,10 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
|
||||
if param == "password" {
|
||||
oldVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", appInstall.Password)
|
||||
newVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", value)
|
||||
if appKey == "redis" {
|
||||
oldVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", appInstall.Password)
|
||||
newVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", value)
|
||||
}
|
||||
_ = appInstallRepo.BatchUpdateBy(map[string]interface{}{
|
||||
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
|
||||
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
|
||||
|
@@ -5,11 +5,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -20,6 +15,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/env"
|
||||
|
||||
@@ -82,20 +83,33 @@ func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||
func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error {
|
||||
var dbConfig dto.AppDatabase
|
||||
if app.Type == "runtime" {
|
||||
var authParam dto.AuthParam
|
||||
paramByte, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(paramByte, &authParam); err != nil {
|
||||
return err
|
||||
}
|
||||
if authParam.RootPassword != "" {
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
switch app.Key {
|
||||
case "mysql", "mariadb", "postgresql":
|
||||
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
authParam := dto.AuthParam{
|
||||
RootPassword: password.(string),
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
}
|
||||
}
|
||||
case "redis":
|
||||
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
authParam := dto.RedisAuthParam{
|
||||
RootPassword: password.(string),
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
}
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
}
|
||||
}
|
||||
if app.Type == "website" || app.Type == "tool" {
|
||||
@@ -103,7 +117,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(paramByte, &dbConfig); err != nil {
|
||||
if err = json.Unmarshal(paramByte, &dbConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -116,7 +130,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
||||
var resourceId uint
|
||||
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
|
||||
iMysqlRepo := repo.NewIMysqlRepo()
|
||||
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName))
|
||||
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldMysqlDb.ID
|
||||
if oldMysqlDb.ID > 0 {
|
||||
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
|
||||
@@ -129,6 +143,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
||||
createMysql.Format = "utf8mb4"
|
||||
createMysql.Permission = "%"
|
||||
createMysql.Password = dbConfig.Password
|
||||
createMysql.From = "local"
|
||||
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -185,15 +200,15 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
|
||||
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
|
||||
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key))
|
||||
if install.App.Key == constant.AppMysql {
|
||||
_ = mysqlRepo.DeleteAll(ctx)
|
||||
_ = mysqlRepo.DeleteLocal(ctx)
|
||||
}
|
||||
uploadDir := fmt.Sprintf("%s/1panel/uploads/app/%s/%s", global.CONF.System.BaseDir, install.App.Key, install.Name)
|
||||
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/app/%s/%s", install.App.Key, install.Name))
|
||||
if _, err := os.Stat(uploadDir); err == nil {
|
||||
_ = os.RemoveAll(uploadDir)
|
||||
}
|
||||
if deleteBackup {
|
||||
localDir, _ := loadLocalDir()
|
||||
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", install.App.Key, install.Name))
|
||||
if _, err := os.Stat(backupDir); err == nil {
|
||||
_ = os.RemoveAll(backupDir)
|
||||
}
|
||||
@@ -227,7 +242,7 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
|
||||
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
}
|
||||
|
||||
func upgradeInstall(installId uint, detailId uint) error {
|
||||
func upgradeInstall(installId uint, detailId uint, backup bool) error {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -239,13 +254,14 @@ func upgradeInstall(installId uint, detailId uint) error {
|
||||
if install.Version == detail.Version {
|
||||
return errors.New("two version is same")
|
||||
}
|
||||
if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
install.Status = constant.Upgrading
|
||||
|
||||
go func() {
|
||||
if backup {
|
||||
if err = NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
|
||||
global.LOG.Errorf(i18n.GetMsgWithMap("ErrAppBackup", map[string]interface{}{"name": install.Name, "err": err.Error()}))
|
||||
}
|
||||
}
|
||||
var upErr error
|
||||
defer func() {
|
||||
if upErr != nil {
|
||||
@@ -268,15 +284,10 @@ func upgradeInstall(installId uint, detailId uint) error {
|
||||
detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
|
||||
}
|
||||
|
||||
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath()))
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if stdout != nil {
|
||||
upErr = errors.New(string(stdout))
|
||||
return
|
||||
}
|
||||
upErr = err
|
||||
return
|
||||
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
|
||||
stdout, _ := cmd.CombinedOutput()
|
||||
if stdout != nil {
|
||||
global.LOG.Errorf("upgrade app [%s] [%s] cp file log : %s ", install.App.Key, install.Name, string(stdout))
|
||||
}
|
||||
|
||||
composeMap := make(map[string]interface{})
|
||||
@@ -330,6 +341,24 @@ func upgradeInstall(installId uint, detailId uint) error {
|
||||
install.Version = detail.Version
|
||||
install.AppDetailId = detailId
|
||||
|
||||
images, err := getImages(install)
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
dockerCli, err := composeV2.NewClient()
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
if err = dockerCli.PullImage(image, true); err != nil {
|
||||
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := compose.Down(install.GetComposePath()); err != nil {
|
||||
if out != "" {
|
||||
upErr = errors.New(out)
|
||||
@@ -387,6 +416,26 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
|
||||
return containerNames, nil
|
||||
}
|
||||
|
||||
func getImages(install model.AppInstall) ([]string, error) {
|
||||
envStr, err := coverEnvJsonToStr(install.Env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imagesMap := make(map[string]struct{})
|
||||
for _, service := range project.AllServices() {
|
||||
imagesMap[service.Image] = struct{}{}
|
||||
}
|
||||
var images []string
|
||||
for k := range imagesMap {
|
||||
images = append(images, k)
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func coverEnvJsonToStr(envJson string) (string, error) {
|
||||
envMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal([]byte(envJson), &envMap)
|
||||
@@ -527,11 +576,15 @@ func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppIns
|
||||
|
||||
// 处理文件夹权限等问题
|
||||
func upAppPre(app model.App, appInstall *model.AppInstall) error {
|
||||
if app.Key == "nexus" {
|
||||
dataPath := path.Join(appInstall.GetPath(), "data")
|
||||
if err := files.NewFileOp().Chown(dataPath, 200, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
dataPath := path.Join(appInstall.GetPath(), "data")
|
||||
fileOp := files.NewFileOp()
|
||||
switch app.Key {
|
||||
case "nexus":
|
||||
return fileOp.ChownR(dataPath, "200", "0", true)
|
||||
case "sftpgo":
|
||||
return fileOp.ChownR(dataPath, "1000", "1000", true)
|
||||
case "pgadmin4":
|
||||
return fileOp.ChownR(dataPath, "5050", "5050", true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -48,10 +48,12 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = settingRepo.Update("Language", info.Language); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mfa.Value == "enable" {
|
||||
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, nil
|
||||
}
|
||||
|
||||
return u.generateSession(c, info.Name, info.AuthMethod)
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,7 @@ type IBackupService interface {
|
||||
BatchDeleteRecord(ids []uint) error
|
||||
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
||||
|
||||
ListFiles(req dto.BackupSearchFile) ([]interface{}, error)
|
||||
ListFiles(req dto.BackupSearchFile) ([]string, error)
|
||||
|
||||
MysqlBackup(db dto.CommonBackup) error
|
||||
MysqlRecover(db dto.CommonRecover) error
|
||||
@@ -273,7 +273,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]interface{}, error) {
|
||||
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]string, error) {
|
||||
backup, err := backupRepo.Get(backupRepo.WithByType(req.Type))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -282,7 +282,21 @@ func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]interface{}, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.ListObjects("system_snapshot/")
|
||||
prefix := "system_snapshot"
|
||||
if len(backup.BackupPath) != 0 {
|
||||
prefix = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), prefix)
|
||||
}
|
||||
files, err := client.ListObjects(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []string
|
||||
for _, file := range files {
|
||||
if len(file) != 0 {
|
||||
datas = append(datas, path.Base(file))
|
||||
}
|
||||
}
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
|
||||
|
@@ -34,7 +34,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
|
||||
}
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
|
||||
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", req.Name, req.DetailName))
|
||||
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
|
||||
if err := handleAppBackup(&install, backupDir, fileName); err != nil {
|
||||
@@ -104,18 +104,16 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
|
||||
return err
|
||||
}
|
||||
|
||||
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
if resource.ID != 0 && resource.ResourceId != 0 {
|
||||
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handleMysqlBackup(mysqlInfo, tmpDir, db.Name, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
return err
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
for _, resource := range resources {
|
||||
if resource.Key == "mysql" {
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +151,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
||||
}
|
||||
|
||||
if !isRollback {
|
||||
rollbackFile := fmt.Sprintf("%s/original/app/%s_%s.tar.gz", global.CONF.System.BaseDir, install.Name, time.Now().Format("20060102150405"))
|
||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format("20060102150405")))
|
||||
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
|
||||
return fmt.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err)
|
||||
}
|
||||
@@ -173,41 +171,55 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
||||
}
|
||||
|
||||
newEnvFile := ""
|
||||
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
if resource.ID != 0 && install.App.Key != "mysql" {
|
||||
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
for _, resource := range resources {
|
||||
if resource.Key == "mysql" {
|
||||
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newDB, envMap, err := reCreateDB(db.ID, oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
|
||||
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName)
|
||||
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
|
||||
envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName
|
||||
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
|
||||
newDB, envMap, err := reCreateDB(db.ID, oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
|
||||
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName)
|
||||
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
|
||||
envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName
|
||||
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
|
||||
|
||||
if err := handleMysqlRecover(mysqlInfo, tmpPath, newDB.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
|
||||
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
|
||||
return err
|
||||
if err := handleMysqlRecover(dto.CommonRecover{
|
||||
Name: newDB.MysqlName,
|
||||
DetailName: newDB.Name,
|
||||
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
|
||||
}, true); err != nil {
|
||||
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appDir := install.GetPath()
|
||||
backPath := fmt.Sprintf("%s_bak", appDir)
|
||||
_ = fileOp.Rename(appDir, backPath)
|
||||
_ = fileOp.CreateDir(appDir, 0755)
|
||||
|
||||
if err := handleUnTar(tmpPath+"/app.tar.gz", fmt.Sprintf("%s/%s", constant.AppInstallDir, install.App.Key)); err != nil {
|
||||
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
|
||||
_ = fileOp.DeleteDir(appDir)
|
||||
_ = fileOp.Rename(backPath, appDir)
|
||||
return err
|
||||
}
|
||||
_ = fileOp.DeleteDir(backPath)
|
||||
|
||||
if len(newEnvFile) != 0 {
|
||||
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name)
|
||||
@@ -223,11 +235,13 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
||||
oldInstall.Status = constant.StatusRunning
|
||||
oldInstall.AppId = install.AppId
|
||||
oldInstall.AppDetailId = install.AppDetailId
|
||||
oldInstall.App.ID = install.AppId
|
||||
if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil {
|
||||
global.LOG.Errorf("save db app install failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -245,6 +259,7 @@ func reCreateDB(dbID uint, oldEnv string) (*model.DatabaseMysql, map[string]inte
|
||||
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
|
||||
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
|
||||
Name: oldName,
|
||||
From: "local",
|
||||
Format: "utf8mb4",
|
||||
Username: oldUser,
|
||||
Password: oldPassword,
|
||||
|
@@ -1,10 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -12,9 +10,9 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -23,24 +21,22 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
targetDir := path.Join(localDir, fmt.Sprintf("database/mysql/%s/%s", req.Name, req.DetailName))
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
||||
|
||||
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, req.Name, req.DetailName)
|
||||
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
|
||||
if err := handleMysqlBackup(app, backupDir, req.DetailName, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
record := &model.BackupRecord{
|
||||
Type: "mysql",
|
||||
Name: app.Name,
|
||||
Name: req.Name,
|
||||
DetailName: req.DetailName,
|
||||
Source: "LOCAL",
|
||||
BackupType: "LOCAL",
|
||||
FileDir: backupDir,
|
||||
FileDir: targetDir,
|
||||
FileName: fileName,
|
||||
}
|
||||
if err := backupRepo.CreateRecord(record); err != nil {
|
||||
@@ -50,26 +46,13 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||
}
|
||||
global.LOG.Infof("recover database %s-%s from backup file %s", req.Name, req.DetailName, req.File)
|
||||
if err := handleMysqlRecover(app, path.Dir(req.File), req.DetailName, path.Base(req.File), false); err != nil {
|
||||
if err := handleMysqlRecover(req, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file := req.File
|
||||
fileName := path.Base(req.File)
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
@@ -106,77 +89,92 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
}()
|
||||
}
|
||||
|
||||
if err := handleMysqlRecover(app, path.Dir(file), req.DetailName, fileName, false); err != nil {
|
||||
req.File = path.Dir(file) + "/" + fileName
|
||||
if err := handleMysqlRecover(req, false); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Info("recover from uploads successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMysqlBackup(app *repo.RootInfo, backupDir, dbName, fileName string) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(backupDir) {
|
||||
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||
}
|
||||
func handleMysqlBackup(name, dbName, targetDir, fileName string) error {
|
||||
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadMysqlClientByFrom(dbInfo.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outfile, _ := os.OpenFile(backupDir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0755)
|
||||
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", backupDir+"/"+fileName)
|
||||
cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, dbName)
|
||||
gzipCmd := exec.Command("gzip", "-cf")
|
||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
||||
gzipCmd.Stdout = outfile
|
||||
_ = gzipCmd.Start()
|
||||
_ = cmd.Run()
|
||||
_ = gzipCmd.Wait()
|
||||
|
||||
backupInfo := client.BackupInfo{
|
||||
Name: dbName,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: targetDir,
|
||||
FileName: fileName,
|
||||
|
||||
Timeout: 300,
|
||||
}
|
||||
if err := cli.Backup(backupInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMysqlRecover(mysqlInfo *repo.RootInfo, recoverDir, dbName, fileName string, isRollback bool) error {
|
||||
func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
|
||||
isOk := false
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.File) {
|
||||
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
|
||||
}
|
||||
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, _, err := LoadMysqlClientByFrom(dbInfo.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isRollback {
|
||||
rollbackFile := fmt.Sprintf("%s/original/database/%s_%s.sql.gz", global.CONF.System.BaseDir, mysqlInfo.Name, time.Now().Format("20060102150405"))
|
||||
if err := handleMysqlBackup(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile)); err != nil {
|
||||
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", mysqlInfo.Name, err)
|
||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/mysql/%s_%s.sql.gz", req.DetailName, time.Now().Format("20060102150405")))
|
||||
if err := cli.Backup(client.BackupInfo{
|
||||
Name: req.DetailName,
|
||||
Format: dbInfo.Format,
|
||||
TargetDir: path.Dir(rollbackFile),
|
||||
FileName: path.Base(rollbackFile),
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", req.DetailName, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := handleMysqlRecover(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile), true); err != nil {
|
||||
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", dbName, rollbackFile, err)
|
||||
return
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: rollbackFile,
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
|
||||
}
|
||||
global.LOG.Infof("rollback mysql db %s from %s successful", dbName, rollbackFile)
|
||||
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
file := recoverDir + "/" + fileName
|
||||
fi, _ := os.Open(file)
|
||||
defer fi.Close()
|
||||
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, dbName)
|
||||
if strings.HasSuffix(fileName, ".gz") {
|
||||
gzipFile, err := os.Open(file)
|
||||
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)
|
||||
if err := cli.Recover(client.RecoverInfo{
|
||||
Name: req.DetailName,
|
||||
Format: dbInfo.Format,
|
||||
SourceFile: req.File,
|
||||
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
isOk = true
|
||||
return nil
|
||||
|
@@ -43,7 +43,7 @@ func (u *BackupService) RedisBackup() error {
|
||||
fileName = fmt.Sprintf("%s.tar.gz", timeNow)
|
||||
}
|
||||
}
|
||||
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
|
||||
if err := handleRedisBackup(redisInfo, backupDir, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
|
||||
suffix = "tar.gz"
|
||||
}
|
||||
}
|
||||
rollbackFile := fmt.Sprintf("%s/original/database/redis/%s_%s.%s", global.CONF.System.BaseDir, redisInfo.Name, time.Now().Format("20060102150405"), suffix)
|
||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/redis/%s_%s.%s", redisInfo.Name, time.Now().Format("20060102150405"), suffix))
|
||||
if err := handleRedisBackup(redisInfo, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
|
||||
return fmt.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err)
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||
}
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
backupDir := fmt.Sprintf("%s/website/%s", localDir, req.Name)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", req.Name))
|
||||
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow)
|
||||
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
|
||||
return err
|
||||
@@ -103,7 +103,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
||||
|
||||
isOk := false
|
||||
if !isRollback {
|
||||
rollbackFile := fmt.Sprintf("%s/original/website/%s_%s.tar.gz", global.CONF.System.BaseDir, website.Alias, time.Now().Format("20060102150405"))
|
||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format("20060102150405")))
|
||||
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
|
||||
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
|
||||
}
|
||||
@@ -221,13 +221,10 @@ func checkValidOfWebsite(oldWebsite, website *model.Website) error {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil)
|
||||
}
|
||||
if oldWebsite.AppInstallID != 0 {
|
||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||
_, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||
if err != nil {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, "app", nil)
|
||||
}
|
||||
if app.App.Type != "website" {
|
||||
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("appType: %s", app.App.Type), nil)
|
||||
}
|
||||
}
|
||||
if oldWebsite.RuntimeID != 0 {
|
||||
if _, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)); err != nil {
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -63,6 +64,8 @@ type IContainerService interface {
|
||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||
ComposeUpdate(req dto.ComposeUpdate) error
|
||||
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
|
||||
|
||||
LoadContainerLogs(req dto.OperationWithNameAndType) string
|
||||
}
|
||||
|
||||
func NewIContainerService() IContainerService {
|
||||
@@ -182,7 +185,7 @@ func (u *ContainerService) List() ([]string, error) {
|
||||
for _, container := range containers {
|
||||
for _, name := range container.Names {
|
||||
if len(name) != 0 {
|
||||
datas = append(datas, strings.TrimLeft(name, "/"))
|
||||
datas = append(datas, strings.TrimPrefix(name, "/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,13 +392,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
if oldContainer.HostConfig.Memory != 0 {
|
||||
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
|
||||
}
|
||||
for _, bind := range oldContainer.HostConfig.Binds {
|
||||
parts := strings.Split(bind, ":")
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
data.Volumes = append(data.Volumes, dto.VolumeHelper{SourceDir: parts[0], ContainerDir: parts[1], Mode: parts[2]})
|
||||
}
|
||||
data.Volumes = loadVolumeBinds(oldContainer.HostConfig.Binds)
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
@@ -432,13 +429,15 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
hostConf := oldContainer.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
if err := loadConfigInfo(req, config, hostConf, &networkConf); err != nil {
|
||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("recreate contianer failed, err: %v", err)
|
||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
||||
return fmt.Errorf("update contianer 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 {
|
||||
@@ -481,13 +480,14 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
}
|
||||
|
||||
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
|
||||
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("recreate contianer failed, err: %v", err)
|
||||
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
|
||||
return fmt.Errorf("upgrade contianer failed, err: %v", err)
|
||||
}
|
||||
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
|
||||
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 {
|
||||
return fmt.Errorf("update successful but start failed, err: %v", err)
|
||||
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -628,6 +628,48 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) string {
|
||||
filePath := ""
|
||||
switch req.Type {
|
||||
case "image-pull", "image-push", "image-build":
|
||||
filePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
|
||||
case "compose-detail", "compose-create":
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
options := types.ContainerListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name))
|
||||
containers, err := client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, container := range containers {
|
||||
config := container.Labels[composeConfigLabel]
|
||||
workdir := container.Labels[composeWorkdirLabel]
|
||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
||||
filePath = config
|
||||
break
|
||||
} else {
|
||||
filePath = workdir
|
||||
break
|
||||
}
|
||||
}
|
||||
if req.Type == "compose-create" {
|
||||
filePath = path.Join(path.Dir(filePath), "compose.log")
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return ""
|
||||
}
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func stringsToMap(list []string) map[string]string {
|
||||
var lableMap = make(map[string]string)
|
||||
for _, label := range list {
|
||||
@@ -795,7 +837,11 @@ func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf
|
||||
config.Labels = stringsToMap(req.Labels)
|
||||
config.ExposedPorts = exposeds
|
||||
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
|
||||
if len(req.Network) != 0 {
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
|
||||
} else {
|
||||
networkConf = &network.NetworkingConfig{}
|
||||
}
|
||||
|
||||
hostConf.AutoRemove = req.AutoRemove
|
||||
hostConf.CPUShares = req.CPUShares
|
||||
@@ -815,3 +861,63 @@ func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *types.NetworkSettings) {
|
||||
ctx := context.Background()
|
||||
|
||||
var oldNetworkConf network.NetworkingConfig
|
||||
if networkConf != nil {
|
||||
for networkKey := range networkConf.Networks {
|
||||
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("recreate after container update failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := client.ContainerStart(ctx, oldContainer.ID, types.ContainerStartOptions{}); err != nil {
|
||||
global.LOG.Errorf("restart after container update failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func loadVolumeBinds(binds []string) []dto.VolumeHelper {
|
||||
var datas []dto.VolumeHelper
|
||||
for _, bind := range binds {
|
||||
parts := strings.Split(bind, ":")
|
||||
var volumeItem dto.VolumeHelper
|
||||
if len(parts) > 3 {
|
||||
continue
|
||||
}
|
||||
volumeItem.SourceDir = parts[0]
|
||||
if len(parts) == 1 {
|
||||
volumeItem.ContainerDir = parts[0]
|
||||
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
|
||||
}
|
||||
|
@@ -157,7 +157,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
global.LOG.Infof("docker-compose.yml %s create successful, start to docker-compose up", req.Name)
|
||||
|
||||
if req.From == "path" {
|
||||
req.Name = path.Base(strings.ReplaceAll(req.Path, "/"+path.Base(req.Path), ""))
|
||||
req.Name = path.Base(path.Dir(req.Path))
|
||||
}
|
||||
logName := path.Dir(req.Path) + "/compose.log"
|
||||
file, err := os.OpenFile(logName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
@@ -181,7 +181,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
_, _ = file.WriteString("docker-compose up successful!")
|
||||
}()
|
||||
|
||||
return logName, nil
|
||||
return req.Name, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
|
@@ -9,6 +9,7 @@ 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"
|
||||
@@ -29,6 +30,8 @@ type ICronjobService interface {
|
||||
Download(down dto.CronjobDownload) (string, error)
|
||||
StartJob(cronjob *model.Cronjob) (int, error)
|
||||
CleanRecord(req dto.CronjobClean) error
|
||||
|
||||
LoadRecordLog(req dto.OperateByID) (string, error)
|
||||
}
|
||||
|
||||
func NewICronjobService() ICronjobService {
|
||||
@@ -80,6 +83,21 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
|
||||
return total, dtoCronjobs, err
|
||||
}
|
||||
|
||||
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
|
||||
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(record.Records); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
content, err := os.ReadFile(record.Records)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
cronjob, err := cronjobRepo.Get(commonRepo.WithByID(req.CronjobID))
|
||||
if err != nil {
|
||||
@@ -123,12 +141,8 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
|
||||
if record.ID == 0 {
|
||||
return "", constant.ErrRecordNotFound
|
||||
}
|
||||
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(record.CronjobID))
|
||||
if cronjob.ID == 0 {
|
||||
return "", constant.ErrRecordNotFound
|
||||
}
|
||||
backup, _ := backupRepo.Get(commonRepo.WithByID(down.BackupAccountID))
|
||||
if cronjob.ID == 0 {
|
||||
if backup.ID == 0 {
|
||||
return "", constant.ErrRecordNotFound
|
||||
}
|
||||
if backup.Type == "LOCAL" || record.FromLocal {
|
||||
@@ -137,15 +151,17 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
|
||||
}
|
||||
return record.File, nil
|
||||
}
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tempPath := fmt.Sprintf("%s/download/%s", constant.DataDir, record.File)
|
||||
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
|
||||
isOK, err := client.Download(record.File, tempPath)
|
||||
if !isOK || err != nil {
|
||||
return "", err
|
||||
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
|
||||
client, err := NewIBackupService().NewClient(&backup)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
|
||||
isOK, err := client.Download(record.File, tempPath)
|
||||
if !isOK || err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return tempPath, nil
|
||||
}
|
||||
|
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||
@@ -118,18 +118,14 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
|
||||
switch cronjob.Type {
|
||||
case "database":
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
paths, err := u.handleDatabase(*cronjob, app, backup, startTime)
|
||||
paths, err := u.handleDatabase(*cronjob, backup, startTime)
|
||||
return strings.Join(paths, ","), err
|
||||
case "website":
|
||||
paths, err := u.handleWebsite(*cronjob, backup, startTime)
|
||||
return strings.Join(paths, ","), err
|
||||
default:
|
||||
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
||||
backupDir := fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
|
||||
itemFileDir := fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
|
||||
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
||||
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||
@@ -252,22 +248,25 @@ func handleUnTar(sourceFile, targetDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInfo, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||
var paths []string
|
||||
localDir, err := loadLocalDir()
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
|
||||
var dblist []string
|
||||
var dbs []model.DatabaseMysql
|
||||
if cronjob.DBName == "all" {
|
||||
mysqlService := NewIMysqlService()
|
||||
dblist, err = mysqlService.ListDBName()
|
||||
dbs, err = mysqlRepo.List()
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
} else {
|
||||
dblist = append(dblist, cronjob.DBName)
|
||||
itemID, _ := (strconv.Atoi(cronjob.DBName))
|
||||
dbs, err = mysqlRepo.List(commonRepo.WithByID(uint(itemID)))
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
|
||||
var client cloud_storage.CloudStorageClient
|
||||
@@ -278,20 +277,21 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
|
||||
}
|
||||
}
|
||||
|
||||
for _, dbName := range dblist {
|
||||
for _, dbInfo := range dbs {
|
||||
var record model.BackupRecord
|
||||
|
||||
record.Type = "mysql"
|
||||
record.Name = app.Name
|
||||
record.Source = "LOCAL"
|
||||
record.BackupType = backup.Type
|
||||
|
||||
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, dbName)
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbName, startTime.Format("20060102150405"))
|
||||
if err = handleMysqlBackup(app, backupDir, dbName, record.FileName); err != nil {
|
||||
record.Name = dbInfo.MysqlName
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/mysql/%s/%s", record.Name, dbInfo.Name))
|
||||
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405"))
|
||||
if err = handleMysqlBackup(dbInfo.MysqlName, dbInfo.Name, backupDir, record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
record.DetailName = dbName
|
||||
|
||||
record.DetailName = dbInfo.Name
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
@@ -429,7 +429,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
|
||||
if err != nil {
|
||||
return paths, err
|
||||
}
|
||||
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", website.PrimaryDomain))
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -20,6 +21,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -28,9 +31,10 @@ import (
|
||||
type MysqlService struct{}
|
||||
|
||||
type IMysqlService interface {
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
ListDBName() ([]string, error)
|
||||
SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error)
|
||||
ListDBOption() ([]dto.MysqlOption, error)
|
||||
Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error)
|
||||
LoadFromRemote(from string) error
|
||||
ChangeAccess(info dto.ChangeDBInfo) error
|
||||
ChangePassword(info dto.ChangeDBInfo) error
|
||||
UpdateVariables(updates []dto.MysqlVariablesUpdate) error
|
||||
@@ -42,14 +46,20 @@ type IMysqlService interface {
|
||||
LoadVariables() (*dto.MysqlVariables, error)
|
||||
LoadBaseInfo() (*dto.DBBaseInfo, error)
|
||||
LoadRemoteAccess() (bool, error)
|
||||
|
||||
LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error)
|
||||
}
|
||||
|
||||
func NewIMysqlService() IMysqlService {
|
||||
return &MysqlService{}
|
||||
}
|
||||
|
||||
func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
|
||||
func (u *MysqlService) SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error) {
|
||||
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize,
|
||||
mysqlRepo.WithByFrom(search.From),
|
||||
commonRepo.WithLikeName(search.Info),
|
||||
commonRepo.WithOrderRuleBy(search.OrderBy, search.Order),
|
||||
)
|
||||
var dtoMysqls []dto.MysqlDBInfo
|
||||
for _, mysql := range mysqls {
|
||||
var item dto.MysqlDBInfo
|
||||
@@ -61,20 +71,17 @@ func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interfa
|
||||
return total, dtoMysqls, err
|
||||
}
|
||||
|
||||
func (u *MysqlService) ListDBName() ([]string, error) {
|
||||
func (u *MysqlService) ListDBOption() ([]dto.MysqlOption, error) {
|
||||
mysqls, err := mysqlRepo.List()
|
||||
var dbNames []string
|
||||
var dbs []dto.MysqlOption
|
||||
for _, mysql := range mysqls {
|
||||
dbNames = append(dbNames, mysql.Name)
|
||||
var item dto.MysqlOption
|
||||
if err := copier.Copy(&item, &mysql); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
dbs = append(dbs, item)
|
||||
}
|
||||
return dbNames, err
|
||||
}
|
||||
|
||||
var formatMap = map[string]string{
|
||||
"utf8": "utf8_general_ci",
|
||||
"utf8mb4": "utf8mb4_general_ci",
|
||||
"gbk": "gbk_chinese_ci",
|
||||
"big5": "big5_chinese_ci",
|
||||
return dbs, err
|
||||
}
|
||||
|
||||
func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) {
|
||||
@@ -82,38 +89,97 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
||||
return nil, buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
if req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
}
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name))
|
||||
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name), remoteDBRepo.WithByFrom(req.From))
|
||||
if mysql.ID != 0 {
|
||||
return nil, constant.ErrRecordExist
|
||||
}
|
||||
if err := copier.Copy(&mysql, &req); err != nil {
|
||||
|
||||
var createItem model.DatabaseMysql
|
||||
if err := copier.Copy(&createItem, &req); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
|
||||
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", req.Name, req.Format, formatMap[req.Format])
|
||||
if err := excSQL(app.ContainerName, app.Password, createSql); err != nil {
|
||||
if strings.Contains(err.Error(), "ERROR 1007") {
|
||||
return nil, buserr.New(constant.ErrDatabaseIsExist)
|
||||
}
|
||||
if req.From == "local" && req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
}
|
||||
|
||||
cli, version, err := LoadMysqlClientByFrom(req.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := u.createUser(app.ContainerName, app.Password, app.Version, req); err != nil {
|
||||
|
||||
if req.From == "local" {
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createItem.MysqlName = app.Name
|
||||
} else {
|
||||
createItem.MysqlName = req.From
|
||||
}
|
||||
defer cli.Close()
|
||||
if err := cli.Create(client.CreateInfo{
|
||||
Name: req.Name,
|
||||
Format: req.Format,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Permission: req.Permission,
|
||||
Version: version,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
global.LOG.Infof("create database %s successful!", req.Name)
|
||||
mysql.MysqlName = app.Name
|
||||
if err := mysqlRepo.Create(ctx, &mysql); err != nil {
|
||||
if err := mysqlRepo.Create(ctx, &createItem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mysql, nil
|
||||
return &createItem, nil
|
||||
}
|
||||
|
||||
func (u *MysqlService) LoadFromRemote(from string) error {
|
||||
client, version, err := LoadMysqlClientByFrom(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mysqlName := from
|
||||
if from == "local" {
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mysqlName = app.Name
|
||||
}
|
||||
|
||||
databases, err := mysqlRepo.List(remoteDBRepo.WithByFrom(from))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
datas, err := client.SyncDB(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, data := range datas {
|
||||
hasOld := false
|
||||
for _, oldData := range databases {
|
||||
if strings.EqualFold(oldData.Name, data.Name) {
|
||||
hasOld = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasOld {
|
||||
var createItem model.DatabaseMysql
|
||||
if err := copier.Copy(&createItem, &data); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
createItem.MysqlName = mysqlName
|
||||
if err := mysqlRepo.Create(context.Background(), &createItem); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
|
||||
@@ -122,52 +188,62 @@ func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
|
||||
|
||||
func (u *MysqlService) DeleteCheck(id uint) ([]string, error) {
|
||||
var appInUsed []string
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return appInUsed, err
|
||||
}
|
||||
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return appInUsed, err
|
||||
}
|
||||
|
||||
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
|
||||
for _, app := range apps {
|
||||
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
|
||||
if appInstall.ID != 0 {
|
||||
appInUsed = append(appInUsed, appInstall.Name)
|
||||
if db.From == "local" {
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return appInUsed, err
|
||||
}
|
||||
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
|
||||
for _, app := range apps {
|
||||
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
|
||||
if appInstall.ID != 0 {
|
||||
appInUsed = append(appInUsed, appInstall.Name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID))
|
||||
for _, app := range apps {
|
||||
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
|
||||
if appInstall.ID != 0 {
|
||||
appInUsed = append(appInUsed, appInstall.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return appInUsed, nil
|
||||
}
|
||||
|
||||
func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error {
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
cli, version, err := LoadMysqlClientByFrom(db.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
if err := cli.Delete(client.DeleteInfo{
|
||||
Name: db.Name,
|
||||
Version: version,
|
||||
Username: db.Username,
|
||||
Permission: db.Permission,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := mysqlRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(app.Version, "5.6") {
|
||||
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
|
||||
|
||||
uploadDir := fmt.Sprintf("%s/1panel/uploads/database/mysql/%s/%s", global.CONF.System.BaseDir, app.Name, db.Name)
|
||||
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/database/mysql/%s/%s", app.Name, db.Name))
|
||||
if _, err := os.Stat(uploadDir); err == nil {
|
||||
_ = os.RemoveAll(uploadDir)
|
||||
}
|
||||
@@ -176,7 +252,7 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
|
||||
if err != nil && !req.ForceDelete {
|
||||
return err
|
||||
}
|
||||
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, db.MysqlName, db.Name)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/mysql/%s/%s", db.MysqlName, db.Name))
|
||||
if _, err := os.Stat(backupDir); err == nil {
|
||||
_ = os.RemoveAll(backupDir)
|
||||
}
|
||||
@@ -192,28 +268,45 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
||||
if cmd.CheckIllegal(info.Value) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
|
||||
var (
|
||||
mysql model.DatabaseMysql
|
||||
err error
|
||||
)
|
||||
if info.ID != 0 {
|
||||
mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
cli, version, err := LoadMysqlClientByFrom(info.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
var (
|
||||
mysqlData model.DatabaseMysql
|
||||
passwordInfo client.PasswordChangeInfo
|
||||
)
|
||||
passwordInfo.Password = info.Value
|
||||
passwordInfo.Timeout = 300
|
||||
passwordInfo.Version = version
|
||||
|
||||
passwordChangeCMD := fmt.Sprintf("set password for '%s'@'%s' = password('%s')", mysql.Username, mysql.Permission, info.Value)
|
||||
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
|
||||
passwordChangeCMD = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED WITH mysql_native_password BY '%s';", mysql.Username, mysql.Permission, info.Value)
|
||||
}
|
||||
if info.ID != 0 {
|
||||
appRess, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysql.ID))
|
||||
mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
passwordInfo.Name = mysqlData.Name
|
||||
passwordInfo.Username = mysqlData.Username
|
||||
passwordInfo.Permission = mysqlData.Permission
|
||||
} else {
|
||||
passwordInfo.Username = "root"
|
||||
}
|
||||
if err := cli.ChangePassword(passwordInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.ID != 0 {
|
||||
var appRess []model.AppInstallResource
|
||||
if info.From == "local" {
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysqlData.ID))
|
||||
} else {
|
||||
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(mysqlData.ID))
|
||||
}
|
||||
for _, appRes := range appRess {
|
||||
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(appRes.AppInstallId))
|
||||
if err != nil {
|
||||
@@ -229,29 +322,11 @@ func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := excuteSql(app.ContainerName, app.Password, passwordChangeCMD); err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Info("excute password change sql successful")
|
||||
_ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"password": info.Value})
|
||||
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"password": info.Value})
|
||||
return nil
|
||||
}
|
||||
|
||||
hosts, err := excuteSqlForRows(app.ContainerName, app.Password, "select host from mysql.user where user='root';")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, host := range hosts {
|
||||
if host == "%" || host == "localhost" {
|
||||
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Value)
|
||||
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
|
||||
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Value)
|
||||
}
|
||||
if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := updateInstallInfoInDB("mysql", "", "password", false, info.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -265,71 +340,38 @@ func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
|
||||
if cmd.CheckIllegal(info.Value) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
var (
|
||||
mysql model.DatabaseMysql
|
||||
err error
|
||||
)
|
||||
if info.ID != 0 {
|
||||
mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Value == mysql.Permission {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
cli, version, err := LoadMysqlClientByFrom(info.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.ID == 0 {
|
||||
mysql.Name = "*"
|
||||
mysql.Username = "root"
|
||||
mysql.Permission = "%"
|
||||
mysql.Password = app.Password
|
||||
}
|
||||
defer cli.Close()
|
||||
var (
|
||||
mysqlData model.DatabaseMysql
|
||||
accessInfo client.AccessChangeInfo
|
||||
)
|
||||
accessInfo.Permission = info.Value
|
||||
accessInfo.Timeout = 300
|
||||
accessInfo.Version = version
|
||||
|
||||
if info.Value != mysql.Permission {
|
||||
var userlist []string
|
||||
if strings.Contains(mysql.Permission, ",") {
|
||||
userlist = strings.Split(mysql.Permission, ",")
|
||||
} else {
|
||||
userlist = append(userlist, mysql.Permission)
|
||||
}
|
||||
for _, user := range userlist {
|
||||
if len(user) != 0 {
|
||||
if strings.HasPrefix(app.Version, "5.6") {
|
||||
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", mysql.Username, user)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.ID == 0 {
|
||||
return nil
|
||||
if info.ID != 0 {
|
||||
mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessInfo.Name = mysqlData.Name
|
||||
accessInfo.Username = mysqlData.Username
|
||||
accessInfo.Password = mysqlData.Password
|
||||
accessInfo.OldPermission = mysqlData.Permission
|
||||
} else {
|
||||
accessInfo.Username = "root"
|
||||
}
|
||||
|
||||
if err := u.createUser(app.ContainerName, app.Password, app.Version, dto.MysqlDBCreate{
|
||||
Username: mysql.Username,
|
||||
Name: mysql.Name,
|
||||
Permission: info.Value,
|
||||
Password: mysql.Password,
|
||||
}); err != nil {
|
||||
if err := cli.ChangeAccess(accessInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := excuteSql(app.ContainerName, app.Password, "flush privileges"); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"permission": info.Value})
|
||||
if mysqlData.ID != 0 {
|
||||
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"permission": info.Value})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -493,51 +535,24 @@ func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) {
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (u *MysqlService) createUser(container, password, version string, req dto.MysqlDBCreate) error {
|
||||
var userlist []string
|
||||
if strings.Contains(req.Permission, ",") {
|
||||
ips := strings.Split(req.Permission, ",")
|
||||
for _, ip := range ips {
|
||||
if len(ip) != 0 {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", req.Username, ip))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", req.Username, req.Permission))
|
||||
func (u *MysqlService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
|
||||
filePath := ""
|
||||
switch req.Type {
|
||||
case "mysql-conf":
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/conf/my.cnf", req.Name))
|
||||
case "redis-conf":
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
|
||||
case "slow-logs":
|
||||
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
|
||||
}
|
||||
|
||||
for _, user := range userlist {
|
||||
if err := excSQL(container, password, fmt.Sprintf("create user %s identified by '%s';", user, req.Password)); err != nil {
|
||||
if strings.Contains(err.Error(), "ERROR 1396") {
|
||||
handleCreateError(container, password, req.Name, userlist, false)
|
||||
return buserr.New(constant.ErrUserIsExist)
|
||||
}
|
||||
handleCreateError(container, password, req.Name, userlist, true)
|
||||
return err
|
||||
}
|
||||
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", req.Name, user)
|
||||
if req.Name == "*" {
|
||||
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
|
||||
}
|
||||
if strings.HasPrefix(version, "5.7") || strings.HasPrefix(version, "5.6") {
|
||||
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password)
|
||||
}
|
||||
if err := excSQL(container, password, grantStr); err != nil {
|
||||
handleCreateError(container, password, req.Name, userlist, true)
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func handleCreateError(contaienr, password, dbName string, userlist []string, dropUser bool) {
|
||||
_ = excSQL(contaienr, password, fmt.Sprintf("drop database `%s`", dbName))
|
||||
if dropUser {
|
||||
for _, user := range userlist {
|
||||
if err := excSQL(contaienr, password, fmt.Sprintf("drop user if exists %s", user)); err != nil {
|
||||
global.LOG.Errorf("drop user failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func excuteSqlForMaps(containerName, password, command string) (map[string]string, error) {
|
||||
@@ -569,31 +584,6 @@ func excuteSqlForRows(containerName, password, command string) ([]string, error)
|
||||
return strings.Split(stdStr, "\n"), nil
|
||||
}
|
||||
|
||||
func excuteSql(containerName, password, command string) error {
|
||||
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
|
||||
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
|
||||
}
|
||||
|
||||
func excSQL(containerName, password, command string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, "docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return buserr.WithDetail(constant.ErrExecTimeOut, containerName, nil)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string {
|
||||
isOn := false
|
||||
hasGroup := false
|
||||
@@ -634,3 +624,41 @@ func updateMyCnf(oldFiles []string, group string, param string, value interface{
|
||||
}
|
||||
return newFiles
|
||||
}
|
||||
|
||||
func LoadMysqlClientByFrom(from string) (mysql.MysqlClient, string, error) {
|
||||
var (
|
||||
dbInfo client.DBInfo
|
||||
version string
|
||||
err error
|
||||
)
|
||||
|
||||
dbInfo.From = from
|
||||
dbInfo.Timeout = 300
|
||||
if from != "local" {
|
||||
databaseItem, err := remoteDBRepo.Get(commonRepo.WithByName(from))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
dbInfo.Address = databaseItem.Address
|
||||
dbInfo.Port = databaseItem.Port
|
||||
dbInfo.Username = databaseItem.Username
|
||||
dbInfo.Password = databaseItem.Password
|
||||
version = databaseItem.Version
|
||||
|
||||
} else {
|
||||
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
dbInfo.Address = app.ContainerName
|
||||
dbInfo.Username = "root"
|
||||
dbInfo.Password = app.Password
|
||||
version = app.Version
|
||||
}
|
||||
|
||||
cli, err := mysql.NewMysqlClient(dbInfo)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return cli, version, nil
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -163,7 +164,7 @@ func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interf
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
|
||||
_ = filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@@ -136,14 +136,14 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
|
||||
|
||||
switch req.Key {
|
||||
case "Registries":
|
||||
req.Value = strings.TrimRight(req.Value, ",")
|
||||
req.Value = strings.TrimSuffix(req.Value, ",")
|
||||
if len(req.Value) == 0 {
|
||||
delete(daemonMap, "insecure-registries")
|
||||
} else {
|
||||
daemonMap["insecure-registries"] = strings.Split(req.Value, ",")
|
||||
}
|
||||
case "Mirrors":
|
||||
req.Value = strings.TrimRight(req.Value, ",")
|
||||
req.Value = strings.TrimSuffix(req.Value, ",")
|
||||
if len(req.Value) == 0 {
|
||||
delete(daemonMap, "registry-mirrors")
|
||||
} else {
|
||||
|
@@ -12,7 +12,8 @@ var (
|
||||
appInstallRepo = repo.NewIAppInstallRepo()
|
||||
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
|
||||
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
remoteDBRepo = repo.NewIRemoteDBRepo()
|
||||
|
||||
imageRepoRepo = repo.NewIImageRepoRepo()
|
||||
composeRepo = repo.NewIComposeTemplateRepo()
|
||||
|
556
backend/app/service/host_tool.go
Normal file
556
backend/app/service/host_tool.go
Normal file
@@ -0,0 +1,556 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ini_conf"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/ini.v1"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HostToolService struct{}
|
||||
|
||||
type IHostToolService interface {
|
||||
GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error)
|
||||
CreateToolConfig(req request.HostToolCreate) error
|
||||
OperateTool(req request.HostToolReq) error
|
||||
OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error)
|
||||
GetToolLog(req request.HostToolLogReq) (string, error)
|
||||
OperateSupervisorProcess(req request.SupervisorProcessConfig) error
|
||||
GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error)
|
||||
OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error)
|
||||
}
|
||||
|
||||
func NewIHostToolService() IHostToolService {
|
||||
return &HostToolService{}
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
|
||||
res := &response.HostToolRes{}
|
||||
res.Type = req.Type
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
supervisorConfig := &response.Supervisor{}
|
||||
if !cmd.Which(constant.Supervisord) {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.IsExist = true
|
||||
serviceExist, _ := systemctl.IsExist(constant.Supervisord)
|
||||
if !serviceExist {
|
||||
serviceExist, _ = systemctl.IsExist(constant.Supervisor)
|
||||
if !serviceExist {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisor
|
||||
}
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisord
|
||||
}
|
||||
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
supervisorConfig.ServiceName = serviceNameSet.Value
|
||||
}
|
||||
|
||||
versionRes, _ := cmd.Exec("supervisord -v")
|
||||
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
|
||||
_, ctlRrr := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = ctlRrr == nil
|
||||
|
||||
active, _ := systemctl.IsActive(supervisorConfig.ServiceName)
|
||||
if active {
|
||||
supervisorConfig.Status = "running"
|
||||
} else {
|
||||
supervisorConfig.Status = "stopped"
|
||||
}
|
||||
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if pathSet.ID != 0 || pathSet.Value != "" {
|
||||
supervisorConfig.ConfigPath = pathSet.Value
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.Init = true
|
||||
}
|
||||
|
||||
servicePath := "/usr/lib/systemd/system/supervisor.service"
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(servicePath) {
|
||||
servicePath = "/usr/lib/systemd/system/supervisord.service"
|
||||
}
|
||||
if fileOp.Stat(servicePath) {
|
||||
startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart")
|
||||
if startCmd != "" {
|
||||
args := strings.Fields(startCmd)
|
||||
cIndex := -1
|
||||
for i, arg := range args {
|
||||
if arg == "-c" {
|
||||
cIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cIndex != -1 && cIndex+1 < len(args) {
|
||||
supervisorConfig.ConfigPath = args[cIndex+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if supervisorConfig.ConfigPath == "" {
|
||||
configPath := "/etc/supervisord.conf"
|
||||
if !fileOp.Stat(configPath) {
|
||||
configPath = "/etc/supervisor/supervisord.conf"
|
||||
if fileOp.Stat(configPath) {
|
||||
supervisorConfig.ConfigPath = configPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.Config = supervisorConfig
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.ConfigPath) {
|
||||
return buserr.New("ErrConfigNotFound")
|
||||
}
|
||||
cfg, err := ini.Load(req.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service, err := cfg.GetSection("include")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKey, err := service.GetKey("files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if targetKey != nil {
|
||||
_, err = service.NewKey(";files", targetKey.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
supervisorDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord")
|
||||
includeDir := path.Join(supervisorDir, "supervisor.d")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
if err = fileOp.CreateDir(includeDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logDir := path.Join(supervisorDir, "log")
|
||||
if !fileOp.Stat(logDir) {
|
||||
if err = fileOp.CreateDir(logDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
includePath := path.Join(includeDir, "*.ini")
|
||||
targetKey.SetValue(includePath)
|
||||
if err = cfg.SaveTo(req.ConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 {
|
||||
if err = settingRepo.Update(constant.SupervisorServiceName, req.ServiceName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = settingRepo.Create(constant.SupervisorServiceName, req.ServiceName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configPathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if configPathSet.ID != 0 {
|
||||
if err = settingRepo.Update(constant.SupervisorConfigPath, req.ConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = settingRepo.Create(constant.SupervisorConfigPath, req.ConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = systemctl.Restart(req.ServiceName); err != nil {
|
||||
global.LOG.Errorf("[init] restart %s failed err %s", req.ServiceName, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
|
||||
serviceName := req.Type
|
||||
if req.Type == constant.Supervisord {
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
serviceName = serviceNameSet.Value
|
||||
}
|
||||
}
|
||||
return systemctl.Operate(req.Operate, serviceName)
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
|
||||
fileOp := files.NewFileOp()
|
||||
res := &response.HostToolConfig{}
|
||||
configPath := ""
|
||||
serviceName := "supervisord"
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if pathSet.ID != 0 || pathSet.Value != "" {
|
||||
configPath = pathSet.Value
|
||||
}
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
serviceName = serviceNameSet.Value
|
||||
}
|
||||
}
|
||||
switch req.Operate {
|
||||
case "get":
|
||||
content, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Content = string(content)
|
||||
case "set":
|
||||
file, err := fileOp.OpenFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldContent, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(configPath, strings.NewReader(req.Content), fileInfo.Mode()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = systemctl.Restart(serviceName); err != nil {
|
||||
_ = fileOp.WriteFile(configPath, bytes.NewReader(oldContent), fileInfo.Mode())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetToolLog(req request.HostToolLogReq) (string, error) {
|
||||
fileOp := files.NewFileOp()
|
||||
logfilePath := ""
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
configPath := "/etc/supervisord.conf"
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if pathSet.ID != 0 || pathSet.Value != "" {
|
||||
configPath = pathSet.Value
|
||||
}
|
||||
logfilePath, _ = ini_conf.GetIniValue(configPath, "supervisord", "logfile")
|
||||
}
|
||||
oldContent, err := fileOp.GetContent(logfilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(oldContent), nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcessConfig) error {
|
||||
var (
|
||||
supervisordDir = path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord")
|
||||
logDir = path.Join(supervisordDir, "log")
|
||||
includeDir = path.Join(supervisordDir, "supervisor.d")
|
||||
outLog = path.Join(logDir, fmt.Sprintf("%s.out.log", req.Name))
|
||||
errLog = path.Join(logDir, fmt.Sprintf("%s.err.log", req.Name))
|
||||
iniPath = path.Join(includeDir, fmt.Sprintf("%s.ini", req.Name))
|
||||
fileOp = files.NewFileOp()
|
||||
)
|
||||
if req.Operate == "update" || req.Operate == "create" {
|
||||
if !fileOp.Stat(req.Dir) {
|
||||
return buserr.New("ErrConfigDirNotFound")
|
||||
}
|
||||
_, err := user.Lookup(req.User)
|
||||
if err != nil {
|
||||
return buserr.WithMap("ErrUserFindErr", map[string]interface{}{"name": req.User, "err": err.Error()}, err)
|
||||
}
|
||||
}
|
||||
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
if fileOp.Stat(iniPath) {
|
||||
return buserr.New("ErrConfigAlreadyExist")
|
||||
}
|
||||
configFile := ini.Empty()
|
||||
section, err := configFile.NewSection(fmt.Sprintf("program:%s", req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = section.NewKey("command", req.Command)
|
||||
_, _ = section.NewKey("directory", req.Dir)
|
||||
_, _ = section.NewKey("autorestart", "true")
|
||||
_, _ = section.NewKey("startsecs", "3")
|
||||
_, _ = section.NewKey("stdout_logfile", outLog)
|
||||
_, _ = section.NewKey("stderr_logfile", errLog)
|
||||
_, _ = section.NewKey("stdout_logfile_maxbytes", "2MB")
|
||||
_, _ = section.NewKey("stderr_logfile_maxbytes", "2MB")
|
||||
_, _ = section.NewKey("user", req.User)
|
||||
_, _ = section.NewKey("priority", "999")
|
||||
_, _ = section.NewKey("numprocs", req.Numprocs)
|
||||
_, _ = section.NewKey("process_name", "%(program_name)s_%(process_num)02d")
|
||||
|
||||
if err = configFile.SaveTo(iniPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return operateSupervisorCtl("reload", "", "")
|
||||
case "update":
|
||||
configFile, err := ini.Load(iniPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
section, err := configFile.GetSection(fmt.Sprintf("program:%s", req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commandKey := section.Key("command")
|
||||
commandKey.SetValue(req.Command)
|
||||
directoryKey := section.Key("directory")
|
||||
directoryKey.SetValue(req.Dir)
|
||||
userKey := section.Key("user")
|
||||
userKey.SetValue(req.User)
|
||||
numprocsKey := section.Key("numprocs")
|
||||
numprocsKey.SetValue(req.Numprocs)
|
||||
|
||||
if err = configFile.SaveTo(iniPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return operateSupervisorCtl("reload", "", "")
|
||||
case "restart":
|
||||
return operateSupervisorCtl("restart", req.Name, "")
|
||||
case "start":
|
||||
return operateSupervisorCtl("start", req.Name, "")
|
||||
case "stop":
|
||||
return operateSupervisorCtl("stop", req.Name, "")
|
||||
case "delete":
|
||||
_ = operateSupervisorCtl("remove", "", req.Name)
|
||||
_ = files.NewFileOp().DeleteFile(iniPath)
|
||||
_ = files.NewFileOp().DeleteFile(outLog)
|
||||
_ = files.NewFileOp().DeleteFile(errLog)
|
||||
_ = operateSupervisorCtl("reload", "", "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) {
|
||||
var (
|
||||
result []response.SupervisorProcessConfig
|
||||
)
|
||||
configDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d")
|
||||
fileList, _ := NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: configDir, Expand: true, Page: 1, PageSize: 100}})
|
||||
if len(fileList.Items) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
for _, configFile := range fileList.Items {
|
||||
f, err := ini.Load(configFile.Path)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get %s file err %s", configFile.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(configFile.Name, ".ini") {
|
||||
config := response.SupervisorProcessConfig{}
|
||||
name := strings.TrimSuffix(configFile.Name, ".ini")
|
||||
config.Name = name
|
||||
section, err := f.GetSection(fmt.Sprintf("program:%s", name))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get %s file section err %s", configFile.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
if command, _ := section.GetKey("command"); command != nil {
|
||||
config.Command = command.Value()
|
||||
}
|
||||
if directory, _ := section.GetKey("directory"); directory != nil {
|
||||
config.Dir = directory.Value()
|
||||
}
|
||||
if user, _ := section.GetKey("user"); user != nil {
|
||||
config.User = user.Value()
|
||||
}
|
||||
if numprocs, _ := section.GetKey("numprocs"); numprocs != nil {
|
||||
config.Numprocs = numprocs.Value()
|
||||
}
|
||||
_ = getProcessStatus(&config)
|
||||
result = append(result, config)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error) {
|
||||
var (
|
||||
fileOp = files.NewFileOp()
|
||||
group = fmt.Sprintf("program:%s", req.Name)
|
||||
configPath = path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d", fmt.Sprintf("%s.ini", req.Name))
|
||||
)
|
||||
switch req.File {
|
||||
case "err.log":
|
||||
logPath, err := ini_conf.GetIniValue(configPath, group, "stderr_logfile")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch req.Operate {
|
||||
case "get":
|
||||
content, err := fileOp.GetContent(logPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
case "clear":
|
||||
if err = fileOp.WriteFile(logPath, strings.NewReader(""), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
case "out.log":
|
||||
logPath, err := ini_conf.GetIniValue(configPath, group, "stdout_logfile")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch req.Operate {
|
||||
case "get":
|
||||
content, err := fileOp.GetContent(logPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
case "clear":
|
||||
if err = fileOp.WriteFile(logPath, strings.NewReader(""), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
case "config":
|
||||
switch req.Operate {
|
||||
case "get":
|
||||
content, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
case "update":
|
||||
if req.Content == "" {
|
||||
return "", buserr.New("ErrConfigIsNull")
|
||||
}
|
||||
if err := fileOp.WriteFile(configPath, strings.NewReader(req.Content), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", operateSupervisorCtl("update", "", req.Name)
|
||||
}
|
||||
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func operateSupervisorCtl(operate, name, group string) error {
|
||||
processNames := []string{operate}
|
||||
if name != "" {
|
||||
includeDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d")
|
||||
f, err := ini.Load(path.Join(includeDir, fmt.Sprintf("%s.ini", name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
section, err := f.GetSection(fmt.Sprintf("program:%s", name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numprocsNum := ""
|
||||
if numprocs, _ := section.GetKey("numprocs"); numprocs != nil {
|
||||
numprocsNum = numprocs.Value()
|
||||
}
|
||||
if numprocsNum == "" {
|
||||
return buserr.New("ErrConfigParse")
|
||||
}
|
||||
processNames = append(processNames, getProcessName(name, numprocsNum)...)
|
||||
}
|
||||
if group != "" {
|
||||
processNames = append(processNames, group)
|
||||
}
|
||||
|
||||
output, err := exec.Command("supervisorctl", processNames...).Output()
|
||||
if err != nil {
|
||||
if output != nil {
|
||||
return errors.New(string(output))
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getProcessName(name, numprocs string) []string {
|
||||
var (
|
||||
processNames []string
|
||||
)
|
||||
num, err := strconv.Atoi(numprocs)
|
||||
if err != nil {
|
||||
return processNames
|
||||
}
|
||||
if num == 1 {
|
||||
processNames = append(processNames, fmt.Sprintf("%s:%s_00", name, name))
|
||||
} else {
|
||||
for i := 0; i < num; i++ {
|
||||
processName := fmt.Sprintf("%s:%s_0%s", name, name, strconv.Itoa(i))
|
||||
if i >= 10 {
|
||||
processName = fmt.Sprintf("%s:%s_%s", name, name, strconv.Itoa(i))
|
||||
}
|
||||
processNames = append(processNames, processName)
|
||||
}
|
||||
}
|
||||
return processNames
|
||||
}
|
||||
|
||||
func getProcessStatus(config *response.SupervisorProcessConfig) error {
|
||||
var (
|
||||
processNames = []string{"status"}
|
||||
)
|
||||
processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...)
|
||||
output, _ := exec.Command("supervisorctl", processNames...).Output()
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 5 {
|
||||
status := response.ProcessStatus{
|
||||
Name: fields[0],
|
||||
Status: fields[1],
|
||||
}
|
||||
if fields[1] == "RUNNING" {
|
||||
status.PID = strings.TrimSuffix(fields[3], ",")
|
||||
status.Uptime = fields[5]
|
||||
} else {
|
||||
status.Msg = strings.Join(fields[2:], " ")
|
||||
}
|
||||
config.Status = append(config.Status, status)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -123,6 +123,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
fileName := "Dockerfile"
|
||||
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
|
||||
if req.From == "edit" {
|
||||
dir := fmt.Sprintf("%s/docker/build/%s", constant.DataDir, strings.ReplaceAll(req.Name, ":", "_"))
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
@@ -156,10 +157,9 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
||||
Remove: true,
|
||||
Labels: stringsToMap(req.Tags),
|
||||
}
|
||||
logName := fmt.Sprintf("%s/build.log", req.Dockerfile)
|
||||
|
||||
pathItem := logName
|
||||
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
logItem := fmt.Sprintf("%s/image_build_%s_%s.log", dockerLogDir, strings.ReplaceAll(req.Name, ":", "_"), time.Now().Format("20060102150405"))
|
||||
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -192,7 +192,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
||||
_, _ = file.WriteString("image build successful!")
|
||||
}()
|
||||
|
||||
return logName, nil
|
||||
return path.Base(logItem), nil
|
||||
}
|
||||
|
||||
func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
||||
@@ -200,15 +200,15 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dockerLogDir := global.CONF.System.TmpDir + "/docker_logs"
|
||||
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
|
||||
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
imageItemName := strings.ReplaceAll(path.Base(req.ImageName), ":", "_")
|
||||
pathItem := fmt.Sprintf("%s/image_pull_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
|
||||
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
logItem := fmt.Sprintf("%s/image_pull_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
|
||||
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -224,7 +224,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
||||
global.LOG.Infof("pull image %s successful!", req.ImageName)
|
||||
_, _ = io.Copy(file, out)
|
||||
}()
|
||||
return pathItem, nil
|
||||
return path.Base(logItem), nil
|
||||
}
|
||||
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.RepoID))
|
||||
if err != nil {
|
||||
@@ -257,7 +257,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
|
||||
_, _ = io.Copy(file, out)
|
||||
_, _ = file.WriteString("image pull successful!")
|
||||
}()
|
||||
return pathItem, nil
|
||||
return path.Base(logItem), nil
|
||||
}
|
||||
|
||||
func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
||||
@@ -354,8 +354,8 @@ func (u *ImageService) ImagePush(req dto.ImagePush) (string, error) {
|
||||
}
|
||||
}
|
||||
imageItemName := strings.ReplaceAll(path.Base(req.Name), ":", "_")
|
||||
pathItem := fmt.Sprintf("%s/image_push_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
|
||||
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
logItem := fmt.Sprintf("%s/image_push_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
|
||||
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -373,7 +373,7 @@ func (u *ImageService) ImagePush(req dto.ImagePush) (string, error) {
|
||||
_, _ = file.WriteString("image push successful!")
|
||||
}()
|
||||
|
||||
return pathItem, nil
|
||||
return path.Base(logItem), nil
|
||||
}
|
||||
|
||||
func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
|
||||
|
@@ -1,9 +1,14 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"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/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@@ -20,6 +25,8 @@ type ILogService interface {
|
||||
CreateOperationLog(operation model.OperationLog) error
|
||||
PageOperationLog(search dto.SearchOpLogWithPage) (int64, interface{}, error)
|
||||
|
||||
LoadSystemLog() (string, error)
|
||||
|
||||
CleanLogs(logtype string) error
|
||||
}
|
||||
|
||||
@@ -74,6 +81,18 @@ func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, inter
|
||||
return total, dtoOps, err
|
||||
}
|
||||
|
||||
func (u *LogService) LoadSystemLog() (string, error) {
|
||||
filePath := path.Join(global.CONF.System.DataDir, "log/1Panel.log")
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func (u *LogService) CleanLogs(logtype string) error {
|
||||
if logtype == "operation" {
|
||||
return logRepo.CleanOperation()
|
||||
|
@@ -167,10 +167,12 @@ func StartMonitor(removeBefore bool, interval string) error {
|
||||
if removeBefore {
|
||||
global.Cron.Remove(cron.EntryID(global.MonitorCronID))
|
||||
}
|
||||
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), NewIMonitorService())
|
||||
imservice := NewIMonitorService()
|
||||
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), imservice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imservice.Run()
|
||||
global.MonitorCronID = int(monitorID)
|
||||
return nil
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ type NginxService struct {
|
||||
}
|
||||
|
||||
type INginxService interface {
|
||||
GetNginxConfig() (response.FileInfo, error)
|
||||
GetNginxConfig() (*response.NginxFile, error)
|
||||
GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error)
|
||||
UpdateConfigByScope(req request.NginxConfigUpdate) error
|
||||
GetStatus() (response.NginxStatus, error)
|
||||
@@ -31,20 +31,17 @@ func NewINginxService() INginxService {
|
||||
return &NginxService{}
|
||||
}
|
||||
|
||||
func (n NginxService) GetNginxConfig() (response.FileInfo, error) {
|
||||
func (n NginxService) GetNginxConfig() (*response.NginxFile, error) {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return response.FileInfo{}, err
|
||||
return nil, err
|
||||
}
|
||||
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
|
||||
info, err := files.NewFileInfo(files.FileOption{
|
||||
Path: configPath,
|
||||
Expand: true,
|
||||
})
|
||||
byteContent, err := files.NewFileOp().GetContent(configPath)
|
||||
if err != nil {
|
||||
return response.FileInfo{}, err
|
||||
return nil, err
|
||||
}
|
||||
return response.FileInfo{FileInfo: *info}, nil
|
||||
return &response.NginxFile{Content: string(byteContent)}, nil
|
||||
}
|
||||
|
||||
func (n NginxService) GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error) {
|
||||
@@ -86,31 +83,32 @@ func (n NginxService) GetStatus() (response.NginxStatus, error) {
|
||||
|
||||
func (n NginxService) UpdateConfigFile(req request.NginxConfigFileUpdate) error {
|
||||
fileOp := files.NewFileOp()
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
filePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Backup {
|
||||
backupPath := path.Join(path.Dir(req.FilePath), "bak")
|
||||
backupPath := path.Join(path.Dir(filePath), "bak")
|
||||
if !fileOp.Stat(backupPath) {
|
||||
if err := fileOp.CreateDir(backupPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newFile := path.Join(backupPath, "nginx.bak"+"-"+time.Now().Format("2006-01-02-15-04-05"))
|
||||
if err := fileOp.Copy(req.FilePath, backupPath); err != nil {
|
||||
if err := fileOp.Copy(filePath, backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.Rename(path.Join(backupPath, "nginx.conf"), newFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
oldContent, err := os.ReadFile(req.FilePath)
|
||||
oldContent, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.WriteFile(req.FilePath, strings.NewReader(req.Content), 0644); err != nil {
|
||||
if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nginxCheckAndReload(string(oldContent), req.FilePath, nginxInstall.ContainerName)
|
||||
return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
|
||||
}
|
||||
|
146
backend/app/service/remote_db.go
Normal file
146
backend/app/service/remote_db.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type RemoteDBService struct{}
|
||||
|
||||
type IRemoteDBService interface {
|
||||
Get(name string) (dto.RemoteDBInfo, error)
|
||||
SearchWithPage(search dto.RemoteDBSearch) (int64, interface{}, error)
|
||||
CheckeRemoteDB(req dto.RemoteDBCreate) bool
|
||||
Create(req dto.RemoteDBCreate) error
|
||||
Update(req dto.RemoteDBUpdate) error
|
||||
Delete(id uint) error
|
||||
List(dbType string) ([]dto.RemoteDBOption, error)
|
||||
}
|
||||
|
||||
func NewIRemoteDBService() IRemoteDBService {
|
||||
return &RemoteDBService{}
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) SearchWithPage(search dto.RemoteDBSearch) (int64, interface{}, error) {
|
||||
total, dbs, err := remoteDBRepo.Page(search.Page, search.PageSize,
|
||||
commonRepo.WithByType(search.Type),
|
||||
commonRepo.WithLikeName(search.Info),
|
||||
remoteDBRepo.WithoutByFrom("local"),
|
||||
)
|
||||
var datas []dto.RemoteDBInfo
|
||||
for _, db := range dbs {
|
||||
var item dto.RemoteDBInfo
|
||||
if err := copier.Copy(&item, &db); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
datas = append(datas, item)
|
||||
}
|
||||
return total, datas, err
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) Get(name string) (dto.RemoteDBInfo, error) {
|
||||
var data dto.RemoteDBInfo
|
||||
remote, err := remoteDBRepo.Get(commonRepo.WithByName(name))
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
if err := copier.Copy(&data, &remote); err != nil {
|
||||
return data, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) List(dbType string) ([]dto.RemoteDBOption, error) {
|
||||
dbs, err := remoteDBRepo.GetList(commonRepo.WithByType(dbType))
|
||||
var datas []dto.RemoteDBOption
|
||||
for _, db := range dbs {
|
||||
var item dto.RemoteDBOption
|
||||
if err := copier.Copy(&item, &db); err != nil {
|
||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
datas = append(datas, item)
|
||||
}
|
||||
return datas, err
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) CheckeRemoteDB(req dto.RemoteDBCreate) bool {
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) Create(req dto.RemoteDBCreate) error {
|
||||
db, _ := remoteDBRepo.Get(commonRepo.WithByName(req.Name))
|
||||
if db.ID != 0 {
|
||||
return constant.ErrRecordExist
|
||||
}
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copier.Copy(&db, &req); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
if err := remoteDBRepo.Create(&db); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) Delete(id uint) error {
|
||||
db, _ := remoteDBRepo.Get(commonRepo.WithByID(id))
|
||||
if db.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
if err := remoteDBRepo.Delete(commonRepo.WithByID(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
if db.From != "local" {
|
||||
if err := mysqlRepo.Delete(context.Background(), remoteDBRepo.WithByFrom(db.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) Update(req dto.RemoteDBUpdate) error {
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["version"] = req.Version
|
||||
upMap["address"] = req.Address
|
||||
upMap["port"] = req.Port
|
||||
upMap["username"] = req.Username
|
||||
upMap["password"] = req.Password
|
||||
upMap["description"] = req.Description
|
||||
return remoteDBRepo.Update(req.ID, upMap)
|
||||
}
|
@@ -98,10 +98,6 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
composeService, err := getComposeService(create.Name, newNameDir, composeContent, envContent, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
runtime := &model.Runtime{
|
||||
Name: create.Name,
|
||||
DockerCompose: string(composeContent),
|
||||
@@ -117,7 +113,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
|
||||
if err = runtimeRepo.Create(context.Background(), runtime); err != nil {
|
||||
return
|
||||
}
|
||||
go buildRuntime(runtime, composeService, "")
|
||||
go buildRuntime(runtime, "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -260,7 +256,6 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composeService, err := getComposeService(runtime.Name, runtimeDir, composeContent, envContent, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -277,6 +272,6 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go buildRuntime(runtime, composeService, imageID)
|
||||
go buildRuntime(runtime, imageID)
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
@@ -8,20 +9,42 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/subosito/gotenv"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func buildRuntime(runtime *model.Runtime, service *docker.ComposeService, oldImageID string) {
|
||||
err := service.ComposeBuild()
|
||||
func buildRuntime(runtime *model.Runtime, oldImageID string) {
|
||||
runtimePath := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
|
||||
composePath := path.Join(runtimePath, "docker-compose.yml")
|
||||
logPath := path.Join(runtimePath, "build.log")
|
||||
|
||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to open log file:", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = logFile.Close()
|
||||
}()
|
||||
|
||||
cmd := exec.Command("docker-compose", "-f", composePath, "build")
|
||||
multiWriterStdout := io.MultiWriter(os.Stdout, logFile)
|
||||
cmd.Stdout = multiWriterStdout
|
||||
var stderrBuf bytes.Buffer
|
||||
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile, os.Stderr)
|
||||
cmd.Stderr = multiWriterStderr
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
runtime.Status = constant.RuntimeError
|
||||
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error()
|
||||
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
|
||||
} else {
|
||||
runtime.Status = constant.RuntimeNormal
|
||||
runtime.Message = ""
|
||||
if oldImageID != "" {
|
||||
client, err := docker.NewClient()
|
||||
if err == nil {
|
||||
@@ -83,25 +106,3 @@ func handleParams(image, runtimeType, runtimeDir string, params map[string]inter
|
||||
envContent = []byte(envStr)
|
||||
return
|
||||
}
|
||||
|
||||
func getComposeService(name, runtimeDir string, composeFile, env []byte, skipNormalization bool) (*docker.ComposeService, error) {
|
||||
project, err := docker.GetComposeProject(name, runtimeDir, composeFile, env, skipNormalization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logPath := path.Join(runtimeDir, "build.log")
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(logPath) {
|
||||
_ = fileOp.DeleteFile(logPath)
|
||||
}
|
||||
file, err := os.Create(logPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
composeService, err := docker.NewComposeService(command.WithOutputStream(file))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
composeService.SetProject(project)
|
||||
return composeService, nil
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -172,7 +173,7 @@ func (u *SettingService) UpdatePort(port uint) error {
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
secretDir := global.CONF.System.BaseDir + "/1panel/secret/"
|
||||
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
|
||||
if req.SSL == "disable" {
|
||||
if err := settingRepo.Update("SSL", "disable"); err != nil {
|
||||
return err
|
||||
@@ -180,8 +181,8 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
if err := settingRepo.Update("SSLType", "self"); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Remove(secretDir + "server.crt")
|
||||
_ = os.Remove(secretDir + "server.key")
|
||||
_ = os.Remove(path.Join(secretDir, "server.crt"))
|
||||
_ = os.Remove(path.Join(secretDir, "server.key"))
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
if err != nil {
|
||||
@@ -220,7 +221,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
}
|
||||
}
|
||||
if req.SSLType == "import" {
|
||||
cert, err := os.OpenFile(secretDir+"server.crt.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
cert, err := os.OpenFile(path.Join(secretDir, "server.crt.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -228,7 +229,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
if _, err := cert.WriteString(req.Cert); err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := os.OpenFile(secretDir+"server.key.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
key, err := os.OpenFile(path.Join(secretDir, "server.key.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -242,10 +243,10 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
if err := fileOp.Rename(secretDir+"server.crt.tmp", secretDir+"server.crt"); err != nil {
|
||||
if err := fileOp.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.Rename(secretDir+"server.key.tmp", secretDir+"server.key"); err != nil {
|
||||
if err := fileOp.Rename(path.Join(secretDir, "server.key.tmp"), path.Join(secretDir, "server.key")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.Update("SSL", req.SSL); err != nil {
|
||||
@@ -278,16 +279,16 @@ func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
|
||||
}
|
||||
switch sslType.Value {
|
||||
case "import":
|
||||
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.crt"); err != nil {
|
||||
if _, err := os.Stat(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")); err != nil {
|
||||
return nil, fmt.Errorf("load server.crt file failed, err: %v", err)
|
||||
}
|
||||
certFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
|
||||
certFile, _ := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"))
|
||||
data.Cert = string(certFile)
|
||||
|
||||
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.key"); err != nil {
|
||||
if _, err := os.Stat(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key")); err != nil {
|
||||
return nil, fmt.Errorf("load server.key file failed, err: %v", err)
|
||||
}
|
||||
keyFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
|
||||
keyFile, _ := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key"))
|
||||
data.Key = string(keyFile)
|
||||
case "select":
|
||||
sslID, err := settingRepo.Get(settingRepo.WithByKey("SSLID"))
|
||||
@@ -341,7 +342,7 @@ func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
|
||||
|
||||
func loadInfoFromCert() (*dto.SSLInfo, error) {
|
||||
var info dto.SSLInfo
|
||||
certFile := global.CONF.System.BaseDir + "/1panel/secret/server.crt"
|
||||
certFile := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
|
||||
if _, err := os.Stat(certFile); err != nil {
|
||||
return &info, err
|
||||
}
|
||||
@@ -369,16 +370,16 @@ func loadInfoFromCert() (*dto.SSLInfo, error) {
|
||||
return &dto.SSLInfo{
|
||||
Domain: strings.Join(domains, ","),
|
||||
Timeout: certObj.NotAfter.Format("2006-01-02 15:04:05"),
|
||||
RootPath: global.CONF.System.BaseDir + "/1panel/secret/server.crt",
|
||||
RootPath: path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkCertValid(domain string) error {
|
||||
certificate, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt.tmp")
|
||||
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key.tmp")
|
||||
key, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key.tmp"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -126,7 +129,7 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
|
||||
timeNow := time.Now().Format("20060102150405")
|
||||
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
|
||||
rootDir := fmt.Sprintf("%s/system/1panel_%s_%s", localDir, versionItem.Value, timeNow)
|
||||
rootDir := path.Join(localDir, fmt.Sprintf("system/1panel_%s_%s", versionItem.Value, timeNow))
|
||||
backupPanelDir := fmt.Sprintf("%s/1panel", rootDir)
|
||||
_ = os.MkdirAll(backupPanelDir, os.ModePerm)
|
||||
backupDockerDir := fmt.Sprintf("%s/docker", rootDir)
|
||||
@@ -142,18 +145,19 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
}
|
||||
_ = snapshotRepo.Create(&snap)
|
||||
go func() {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
_ = os.RemoveAll(rootDir)
|
||||
}()
|
||||
fileOp := files.NewFileOp()
|
||||
|
||||
dockerDataDir, liveRestoreStatus, err := u.loadDockerDataDir()
|
||||
if err != nil {
|
||||
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
snapJson := SnapshotJson{
|
||||
BaseDir: global.CONF.System.BaseDir,
|
||||
BackupDataDir: localDir,
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if err := u.handleDockerDatas(fileOp, "snapshot", dockerDataDir, backupDockerDir); err != nil {
|
||||
|
||||
if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", backupDockerDir); err != nil {
|
||||
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -180,25 +184,20 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "snapshot", global.CONF.System.BaseDir+"/1panel", backupPanelDir, localDir, dockerDataDir); err != nil {
|
||||
dataDir := path.Join(global.CONF.System.BaseDir, "1panel")
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "snapshot", dataDir, backupPanelDir, localDir, snapJson.DockerDataDir); err != nil {
|
||||
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
snapJson.PanelDataDir = dataDir
|
||||
|
||||
snapJson := SnapshotJson{
|
||||
BaseDir: global.CONF.System.BaseDir,
|
||||
DockerDataDir: dockerDataDir,
|
||||
BackupDataDir: localDir,
|
||||
PanelDataDir: global.CONF.System.BaseDir + "/1panel",
|
||||
LiveRestoreEnabled: liveRestoreStatus,
|
||||
}
|
||||
if err := u.saveJson(snapJson, rootDir); err != nil {
|
||||
updateSnapshotStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("save snapshot json failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := handleTar(rootDir, fmt.Sprintf("%s/system", localDir), fmt.Sprintf("1panel_%s_%s.tar.gz", versionItem.Value, timeNow), ""); err != nil {
|
||||
if err := handleTar(rootDir, path.Join(localDir, "system"), fmt.Sprintf("1panel_%s_%s.tar.gz", versionItem.Value, timeNow), ""); err != nil {
|
||||
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -207,16 +206,16 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
|
||||
global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type)
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading})
|
||||
localPath := fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow)
|
||||
itemBackupPath := strings.TrimLeft(backup.BackupPath, "/")
|
||||
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
|
||||
localPath := path.Join(localDir, fmt.Sprintf("system/1panel_%s_%s.tar.gz", versionItem.Value, timeNow))
|
||||
itemBackupPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemBackupPath = strings.TrimSuffix(itemBackupPath, "/")
|
||||
if ok, err := backupAccount.Upload(localPath, fmt.Sprintf("%s/system_snapshot/1panel_%s_%s.tar.gz", itemBackupPath, versionItem.Value, timeNow)); err != nil || !ok {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err)
|
||||
return
|
||||
}
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow))
|
||||
_ = os.RemoveAll(path.Join(localDir, fmt.Sprintf("system/1panel_%s_%s.tar.gz", versionItem.Value, timeNow)))
|
||||
|
||||
global.LOG.Infof("upload snapshot to %s success", backup.Type)
|
||||
}()
|
||||
@@ -232,6 +231,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
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
|
||||
@@ -248,7 +248,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseDir := fmt.Sprintf("%s/system/%s", localDir, snap.Name)
|
||||
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)
|
||||
}
|
||||
@@ -256,13 +256,17 @@ 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.TrimLeft(backup.BackupPath, "/")
|
||||
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
|
||||
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 {
|
||||
@@ -296,39 +300,51 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
if snap.InterruptStep == "Readjson" {
|
||||
isReTry = false
|
||||
}
|
||||
u.OriginalPath = fmt.Sprintf("%s/original_%s", snapJson.BaseDir, snap.Name)
|
||||
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 = global.CONF.System.BaseDir + "/1panel"
|
||||
snapJson.OldPanelDataDir = path.Join(global.CONF.System.BaseDir, "1panel")
|
||||
snapJson.OldBackupDataDir = localDir
|
||||
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
|
||||
liveRestore := false
|
||||
if !isReTry || snap.InterruptStep == "LoadDockerJson" {
|
||||
snapJson.OldDockerDataDir, liveRestore, err = u.loadDockerDataDir()
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, "LoadDockerJson", constant.StatusFailed, fmt.Sprintf("load docker data dir failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if liveRestore {
|
||||
if err := u.updateLiveRestore(false); err != nil {
|
||||
updateRecoverStatus(snap.ID, "UpdateLiveRestore", constant.StatusFailed, fmt.Sprintf("update docker daemon.json live-restore conf failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
_ = u.saveJson(snapJson, rootDir)
|
||||
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if !isReTry || snap.InterruptStep == "DockerDir" {
|
||||
if err := u.handleDockerDatas(fileOp, operation, rootDir, snapJson.DockerDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
|
||||
return
|
||||
if !isNewSnapshot {
|
||||
if !isReTry || snap.InterruptStep == "LoadDockerJson" {
|
||||
snapJson.OldDockerDataDir, liveRestore, err = u.loadDockerDataDir()
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, "LoadDockerJson", constant.StatusFailed, fmt.Sprintf("load docker data dir failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
if liveRestore {
|
||||
if err := u.updateLiveRestore(false); err != nil {
|
||||
updateRecoverStatus(snap.ID, "UpdateLiveRestore", constant.StatusFailed, fmt.Sprintf("update docker daemon.json live-restore conf failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
_ = u.saveJson(snapJson, rootDir)
|
||||
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if !isReTry || snap.InterruptStep == "DockerDir" {
|
||||
if err := u.handleDockerDatas(fileOp, operation, rootDir, snapJson.DockerDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
} else {
|
||||
if !isReTry || snap.InterruptStep == "DockerDir" {
|
||||
if err := u.handleDockerDatasWithSave(fileOp, operation, rootDir, ""); err != nil {
|
||||
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
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())
|
||||
@@ -375,6 +391,11 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
}
|
||||
isReTry = false
|
||||
}
|
||||
|
||||
if isNewSnapshot {
|
||||
_ = rebuildAllAppInstall()
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(rootDir)
|
||||
global.LOG.Info("recover successful")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
@@ -396,30 +417,59 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
isNewSnapshot := isNewSnapVersion(snap.Version)
|
||||
|
||||
rootDir := fmt.Sprintf("%s/system/%s/%s", localDir, snap.Name, snap.Name)
|
||||
rootDir := path.Join(localDir, fmt.Sprintf("system/%s/%s", snap.Name, snap.Name))
|
||||
|
||||
_ = settingRepo.Update("SystemStatus", "Rollbacking")
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
|
||||
go func() {
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
|
||||
if err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
u.OriginalPath = fmt.Sprintf("%s/original_%s", snapJson.OldBaseDir, snap.Name)
|
||||
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.OldBaseDir, snap.Name)
|
||||
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = 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
|
||||
}
|
||||
if snap.InterruptStep == "DockerDir" {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
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 {
|
||||
@@ -427,17 +477,6 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "DaemonJson" {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return
|
||||
}
|
||||
if snapJson.LiveRestoreEnabled {
|
||||
if err := u.updateLiveRestore(true); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if snap.InterruptStep == "UpdateLiveRestore" {
|
||||
_, _ = cmd.Exec("systemctl restart dockere")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -540,6 +579,56 @@ func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation strin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDockerDatasWithSave(fileOp files.FileOp, operation, source, target string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
appInstalls, err := appInstallRepo.ListBy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageRegex := regexp.MustCompile(`image:\s*(.*)`)
|
||||
var imageSaveList []string
|
||||
existStr, _ := cmd.Exec("docker images | awk '{print $1\":\"$2}' | grep -v REPOSITORY:TAG")
|
||||
existImages := strings.Split(existStr, "\n")
|
||||
duplicateMap := make(map[string]bool)
|
||||
for _, app := range appInstalls {
|
||||
matches := imageRegex.FindAllStringSubmatch(app.DockerCompose, -1)
|
||||
for _, match := range matches {
|
||||
for _, existImage := range existImages {
|
||||
if match[1] == existImage && !duplicateMap[match[1]] {
|
||||
imageSaveList = append(imageSaveList, match[1])
|
||||
duplicateMap[match[1]] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(target, "docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "recover":
|
||||
if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", u.OriginalPath); err != nil {
|
||||
return fmt.Errorf("backup docker data failed, err: %v", err)
|
||||
}
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "re-recover":
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
case "rollback":
|
||||
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker_image.tar"))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
}
|
||||
}
|
||||
global.LOG.Info("handle docker data successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string, source, target string) error {
|
||||
daemonJsonPath := "/etc/docker/daemon.json"
|
||||
if operation == "snapshot" || operation == "recover" {
|
||||
@@ -591,6 +680,7 @@ func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation strin
|
||||
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" {
|
||||
@@ -668,7 +758,7 @@ func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation strin
|
||||
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
exclusionRules := "./tmp;./cache;./db/1Panel.db-*;"
|
||||
exclusionRules := "./tmp;./log;./cache;./db/1Panel.db-*;"
|
||||
if strings.Contains(backupDir, source) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
|
||||
}
|
||||
@@ -680,7 +770,7 @@ func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, ope
|
||||
return fmt.Errorf("backup panel data failed, err: %v", err)
|
||||
}
|
||||
case "recover":
|
||||
exclusionRules := "./tmp/;./cache;"
|
||||
exclusionRules := "./tmp;./log;./cache;./db/1Panel.db-*;"
|
||||
if strings.Contains(backupDir, target) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(backupDir, target, "") + ";")
|
||||
}
|
||||
@@ -694,14 +784,17 @@ func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, ope
|
||||
}
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": constant.StatusWaiting})
|
||||
|
||||
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
|
||||
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"))
|
||||
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"))
|
||||
if err := u.handleUnTar(source+"/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("rollback panel data failed, err: %v", err)
|
||||
}
|
||||
@@ -728,8 +821,9 @@ func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
|
||||
return err
|
||||
}
|
||||
for _, snap := range backups {
|
||||
if _, err := os.Stat(fmt.Sprintf("%s/system/%s/%s.tar.gz", localDir, snap.Name, snap.Name)); err == nil {
|
||||
_ = os.Remove(fmt.Sprintf("%s/system/%s/%s.tar.gz", localDir, snap.Name, snap.Name))
|
||||
itemFile := path.Join(localDir, fmt.Sprintf("system/%s/%s.tar.gz", snap.Name, snap.Name))
|
||||
if _, err := os.Stat(itemFile); err == nil {
|
||||
_ = os.Remove(itemFile)
|
||||
}
|
||||
}
|
||||
if err := snapshotRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
|
||||
@@ -866,8 +960,10 @@ func (u *SnapshotService) handleTar(sourceDir, targetDir, name, exclusionRules s
|
||||
global.LOG.Debug(commands)
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
||||
return errors.New(stdout)
|
||||
if len(stdout) != 0 {
|
||||
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
||||
return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -883,8 +979,59 @@ func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
|
||||
global.LOG.Debug(commands)
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
|
||||
return errors.New(stdout)
|
||||
if len(stdout) != 0 {
|
||||
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
|
||||
return fmt.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rebuildAllAppInstall() error {
|
||||
appInstalls, err := appInstallRepo.ListBy()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get all app installed for rebuild failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
for _, install := range appInstalls {
|
||||
_ = rebuildApp(install)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@@ -32,6 +32,8 @@ type ISSHService interface {
|
||||
GenerateSSH(req dto.GenerateSSH) error
|
||||
LoadSSHSecret(mode string) (string, error)
|
||||
LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error)
|
||||
|
||||
LoadSSHConf() (string, error)
|
||||
}
|
||||
|
||||
func NewISSHService() ISSHService {
|
||||
@@ -206,8 +208,13 @@ func (u *SSHService) LoadSSHSecret(mode string) (string, error) {
|
||||
return string(file), err
|
||||
}
|
||||
|
||||
type sshFileItem struct {
|
||||
Name string
|
||||
Year int
|
||||
}
|
||||
|
||||
func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||
var fileList []string
|
||||
var fileList []sshFileItem
|
||||
var data dto.SSHLog
|
||||
baseDir := "/var/log"
|
||||
if err := filepath.Walk(baseDir, func(pathItem string, info os.FileInfo, err error) error {
|
||||
@@ -215,12 +222,15 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") {
|
||||
if strings.HasSuffix(info.Name(), ".gz") {
|
||||
if !strings.HasSuffix(info.Name(), ".gz") {
|
||||
fileList = append(fileList, sshFileItem{Name: pathItem, Year: info.ModTime().Year()})
|
||||
return nil
|
||||
}
|
||||
itemFileName := strings.TrimSuffix(pathItem, ".gz")
|
||||
if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) {
|
||||
if err := handleGunzip(pathItem); err == nil {
|
||||
fileList = append(fileList, strings.ReplaceAll(pathItem, ".gz", ""))
|
||||
fileList = append(fileList, sshFileItem{Name: itemFileName, Year: info.ModTime().Year()})
|
||||
}
|
||||
} else {
|
||||
fileList = append(fileList, pathItem)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -234,80 +244,73 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||
command = fmt.Sprintf(" | grep '%s'", req.Info)
|
||||
}
|
||||
|
||||
for i := 0; i < len(fileList); i++ {
|
||||
withAppend := len(data.Logs) < req.Page*req.PageSize
|
||||
if req.Status != constant.StatusSuccess {
|
||||
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
|
||||
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
|
||||
dataItem, itemTotal := loadFailedSecureDatas(commandItem, withAppend)
|
||||
data.FailedCount += itemTotal
|
||||
data.TotalCount += itemTotal
|
||||
data.Logs = append(data.Logs, dataItem...)
|
||||
}
|
||||
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
|
||||
commandItem := fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command)
|
||||
dataItem, itemTotal := loadFailedAuthDatas(commandItem, withAppend)
|
||||
data.FailedCount += itemTotal
|
||||
data.TotalCount += itemTotal
|
||||
data.Logs = append(data.Logs, dataItem...)
|
||||
}
|
||||
}
|
||||
if req.Status != constant.StatusFailed {
|
||||
commandItem := fmt.Sprintf("cat %s | grep -a Accepted %s", fileList[i], command)
|
||||
dataItem, itemTotal := loadSuccessDatas(commandItem, withAppend)
|
||||
data.TotalCount += itemTotal
|
||||
data.Logs = append(data.Logs, dataItem...)
|
||||
}
|
||||
}
|
||||
data.SuccessfulCount = data.TotalCount - data.FailedCount
|
||||
if len(data.Logs) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var itemDatas []dto.SSHHistory
|
||||
total, start, end := len(data.Logs), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
itemDatas = make([]dto.SSHHistory, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
itemDatas = data.Logs[start:end]
|
||||
}
|
||||
data.Logs = itemDatas
|
||||
|
||||
timeNow := time.Now()
|
||||
showCountFrom := (req.Page - 1) * req.PageSize
|
||||
showCountTo := req.Page * req.PageSize
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||
qqWry, err := qqwry.NewQQwry()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load qqwry datas failed: %s", err)
|
||||
}
|
||||
var itemLogs []dto.SSHHistory
|
||||
for i := 0; i < len(data.Logs); i++ {
|
||||
data.Logs[i].Area = qqWry.Find(data.Logs[i].Address).Area
|
||||
data.Logs[i].Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", timeNow.Year(), data.Logs[i].DateStr), nyc)
|
||||
itemLogs = append(itemLogs, data.Logs[i])
|
||||
for _, file := range fileList {
|
||||
commandItem := ""
|
||||
if strings.HasPrefix(path.Base(file.Name), "secure") {
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||
case constant.StatusFailed:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", file.Name, command)
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' | grep -v 'invalid' %s", file.Name, command)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||
case constant.StatusFailed:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", file.Name, command)
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file.Name, command)
|
||||
}
|
||||
}
|
||||
dataItem, successCount, failedCount := loadSSHData(commandItem, showCountFrom, showCountTo, file.Year, qqWry, nyc)
|
||||
data.FailedCount += failedCount
|
||||
data.TotalCount += successCount + failedCount
|
||||
showCountFrom = showCountFrom - (successCount + failedCount)
|
||||
showCountTo = showCountTo - (successCount + failedCount)
|
||||
data.Logs = append(data.Logs, dataItem...)
|
||||
}
|
||||
data.Logs = itemLogs
|
||||
|
||||
data.SuccessfulCount = data.TotalCount - data.FailedCount
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func sortFileList(fileNames []string) []string {
|
||||
func (u *SSHService) LoadSSHConf() (string, error) {
|
||||
if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
content, err := os.ReadFile("/etc/ssh/sshd_config")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func sortFileList(fileNames []sshFileItem) []sshFileItem {
|
||||
if len(fileNames) < 2 {
|
||||
return fileNames
|
||||
}
|
||||
if strings.HasPrefix(path.Base(fileNames[0]), "secure") {
|
||||
var itemFile []string
|
||||
if strings.HasPrefix(path.Base(fileNames[0].Name), "secure") {
|
||||
var itemFile []sshFileItem
|
||||
sort.Slice(fileNames, func(i, j int) bool {
|
||||
return fileNames[i] > fileNames[j]
|
||||
return fileNames[i].Name > fileNames[j].Name
|
||||
})
|
||||
itemFile = append(itemFile, fileNames[len(fileNames)-1])
|
||||
itemFile = append(itemFile, fileNames[:len(fileNames)-2]...)
|
||||
return itemFile
|
||||
}
|
||||
sort.Slice(fileNames, func(i, j int) bool {
|
||||
return fileNames[i] < fileNames[j]
|
||||
return fileNames[i].Name < fileNames[j].Name
|
||||
})
|
||||
return fileNames
|
||||
}
|
||||
@@ -342,109 +345,111 @@ func updateSSHConf(oldFiles []string, param string, value interface{}) []string
|
||||
return newFiles
|
||||
}
|
||||
|
||||
func loadSuccessDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
|
||||
func loadSSHData(command string, showCountFrom, showCountTo, currentYear int, qqWry *qqwry.QQwry, nyc *time.Location) ([]dto.SSHHistory, int, int) {
|
||||
var (
|
||||
datas []dto.SSHHistory
|
||||
totalNum int
|
||||
datas []dto.SSHHistory
|
||||
successCount int
|
||||
failedCount int
|
||||
)
|
||||
stdout2, err := cmd.Exec(command)
|
||||
if err == nil {
|
||||
lines := strings.Split(string(stdout2), "\n")
|
||||
if len(lines) == 0 {
|
||||
return datas, 0
|
||||
}
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
parts := strings.Fields(lines[i])
|
||||
if len(parts) < 14 {
|
||||
continue
|
||||
}
|
||||
totalNum++
|
||||
if withAppend {
|
||||
historyItem := dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[6],
|
||||
User: parts[8],
|
||||
Address: parts[10],
|
||||
Port: parts[12],
|
||||
Status: constant.StatusSuccess,
|
||||
if err != nil {
|
||||
return datas, 0, 0
|
||||
}
|
||||
lines := strings.Split(string(stdout2), "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
var itemData dto.SSHHistory
|
||||
switch {
|
||||
case strings.Contains(lines[i], "Failed password for"):
|
||||
itemData = loadFailedSecureDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area = qqWry.Find(itemData.Address).Area
|
||||
itemData.Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, itemData.DateStr), nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
datas = append(datas, historyItem)
|
||||
failedCount++
|
||||
}
|
||||
case strings.Contains(lines[i], "Connection closed by authenticating user"):
|
||||
itemData = loadFailedAuthDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area = qqWry.Find(itemData.Address).Area
|
||||
itemData.Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, itemData.DateStr), nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
failedCount++
|
||||
}
|
||||
case strings.Contains(lines[i], "Accepted "):
|
||||
itemData = loadSuccessDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area = qqWry.Find(itemData.Address).Area
|
||||
itemData.Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, itemData.DateStr), nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
return datas, totalNum
|
||||
return datas, successCount, failedCount
|
||||
}
|
||||
|
||||
func loadFailedAuthDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
|
||||
var (
|
||||
datas []dto.SSHHistory
|
||||
totalNum int
|
||||
)
|
||||
stdout2, err := cmd.Exec(command)
|
||||
if err == nil {
|
||||
lines := strings.Split(string(stdout2), "\n")
|
||||
if len(lines) == 0 {
|
||||
return datas, 0
|
||||
}
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
parts := strings.Fields(lines[i])
|
||||
if len(parts) < 14 {
|
||||
continue
|
||||
}
|
||||
totalNum++
|
||||
if withAppend {
|
||||
historyItem := dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[8],
|
||||
User: parts[10],
|
||||
Address: parts[11],
|
||||
Port: parts[13],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
if strings.Contains(lines[i], ": ") {
|
||||
historyItem.Message = strings.Split(lines[i], ": ")[1]
|
||||
}
|
||||
datas = append(datas, historyItem)
|
||||
}
|
||||
}
|
||||
func loadSuccessDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 14 {
|
||||
return data
|
||||
}
|
||||
return datas, totalNum
|
||||
data = dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[6],
|
||||
User: parts[8],
|
||||
Address: parts[10],
|
||||
Port: parts[12],
|
||||
Status: constant.StatusSuccess,
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFailedSecureDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
|
||||
var (
|
||||
datas []dto.SSHHistory
|
||||
totalNum int
|
||||
)
|
||||
stdout2, err := cmd.Exec(command)
|
||||
if err == nil {
|
||||
lines := strings.Split(string(stdout2), "\n")
|
||||
if len(lines) == 0 {
|
||||
return datas, 0
|
||||
}
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
parts := strings.Fields(lines[i])
|
||||
if len(parts) < 14 {
|
||||
continue
|
||||
}
|
||||
totalNum++
|
||||
if withAppend {
|
||||
historyItem := dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[6],
|
||||
User: parts[8],
|
||||
Address: parts[10],
|
||||
Port: parts[12],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
if strings.Contains(lines[i], ": ") {
|
||||
historyItem.Message = strings.Split(lines[i], ": ")[1]
|
||||
}
|
||||
datas = append(datas, historyItem)
|
||||
}
|
||||
}
|
||||
func loadFailedAuthDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 14 {
|
||||
return data
|
||||
}
|
||||
return datas, totalNum
|
||||
data = dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[8],
|
||||
User: parts[10],
|
||||
Address: parts[11],
|
||||
Port: parts[13],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFailedSecureDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 14 {
|
||||
return data
|
||||
}
|
||||
data = dto.SSHHistory{
|
||||
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||
AuthMode: parts[6],
|
||||
User: parts[8],
|
||||
Address: parts[10],
|
||||
Port: parts[12],
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func handleGunzip(path string) error {
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -86,8 +87,8 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
global.LOG.Info("start to upgrade now...")
|
||||
fileOp := files.NewFileOp()
|
||||
timeStr := time.Now().Format("20060102150405")
|
||||
rootDir := fmt.Sprintf("%s/upgrade_%s/downloads", global.CONF.System.TmpDir, timeStr)
|
||||
originalDir := fmt.Sprintf("%s/upgrade_%s/original", global.CONF.System.TmpDir, timeStr)
|
||||
rootDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/downloads", timeStr))
|
||||
originalDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/original", timeStr))
|
||||
if err := os.MkdirAll(rootDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -16,6 +17,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/env"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
@@ -54,20 +58,26 @@ type IWebsiteService interface {
|
||||
CreateWebsiteDomain(create request.WebsiteDomainCreate) (model.WebsiteDomain, error)
|
||||
GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error)
|
||||
DeleteWebsiteDomain(domainId uint) error
|
||||
|
||||
GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error)
|
||||
UpdateNginxConfigByScope(req request.NginxConfigUpdate) error
|
||||
GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error)
|
||||
UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error
|
||||
GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error)
|
||||
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
|
||||
PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
|
||||
GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error)
|
||||
UpdateWafConfig(req request.WebsiteWafUpdate) error
|
||||
UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error
|
||||
OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error)
|
||||
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
|
||||
ChangePHPVersion(req request.WebsitePHPVersionReq) error
|
||||
|
||||
GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error)
|
||||
UpdateRewriteConfig(req request.NginxRewriteUpdate) error
|
||||
UpdateSiteDir(req request.WebsiteUpdateDir) error
|
||||
@@ -79,6 +89,9 @@ type IWebsiteService interface {
|
||||
UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
|
||||
GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error)
|
||||
UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error)
|
||||
OperateRedirect(req request.NginxRedirectReq) (err error)
|
||||
GetRedirect(id uint) (res []response.NginxRedirectConfig, err error)
|
||||
UpdateRedirectFile(req request.NginxRedirectUpdate) (err error)
|
||||
}
|
||||
|
||||
func NewIWebsiteService() IWebsiteService {
|
||||
@@ -339,6 +352,11 @@ func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if website.IPV6 != req.IPV6 {
|
||||
if err := changeIPV6(website, req.IPV6); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
website.PrimaryDomain = req.PrimaryDomain
|
||||
website.WebsiteGroupID = req.WebsiteGroupID
|
||||
website.Remark = req.Remark
|
||||
@@ -408,13 +426,13 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
||||
|
||||
if req.DeleteBackup {
|
||||
localDir, _ := loadLocalDir()
|
||||
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.Alias)
|
||||
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", website.Alias))
|
||||
if _, err := os.Stat(backupDir); err == nil {
|
||||
_ = os.RemoveAll(backupDir)
|
||||
}
|
||||
global.LOG.Infof("delete website %s backups successful", website.Alias)
|
||||
}
|
||||
uploadDir := fmt.Sprintf("%s/1panel/uploads/website/%s", global.CONF.System.BaseDir, website.Alias)
|
||||
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/website/%s", website.Alias))
|
||||
if _, err := os.Stat(uploadDir); err == nil {
|
||||
_ = os.RemoveAll(uploadDir)
|
||||
}
|
||||
@@ -831,7 +849,6 @@ func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (response.Websit
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
res.FilePath = filePath
|
||||
res.Content = string(content)
|
||||
|
||||
return res, nil
|
||||
@@ -1028,7 +1045,7 @@ func (w WebsiteService) GetPHPConfig(id uint) (*response.PHPConfig, error) {
|
||||
phpConfigPath := path.Join(appInstall.GetPath(), "conf", "php.ini")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(phpConfigPath) {
|
||||
return nil, buserr.WithDetail(constant.ErrFileCanNotRead, "php.ini", nil)
|
||||
return nil, buserr.WithMap("ErrFileNotFound", map[string]interface{}{"name": "php.ini"}, nil)
|
||||
}
|
||||
params := make(map[string]string)
|
||||
configFile, err := fileOp.OpenFile(phpConfigPath)
|
||||
@@ -1082,7 +1099,7 @@ func (w WebsiteService) UpdatePHPConfig(req request.WebsitePHPConfigUpdate) (err
|
||||
phpConfigPath := path.Join(appInstall.GetPath(), "conf", "php.ini")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(phpConfigPath) {
|
||||
return buserr.WithDetail(constant.ErrFileCanNotRead, "php.ini", nil)
|
||||
return buserr.WithMap("ErrFileNotFound", map[string]interface{}{"name": "php.ini"}, nil)
|
||||
}
|
||||
configFile, err := fileOp.OpenFile(phpConfigPath)
|
||||
if err != nil {
|
||||
@@ -1174,6 +1191,108 @@ func (w WebsiteService) UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldRuntime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.Resource == constant.ResourceLocal || oldRuntime.Resource == constant.ResourceLocal {
|
||||
return buserr.New("ErrPHPResource")
|
||||
}
|
||||
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envs := make(map[string]interface{})
|
||||
if err = json.Unmarshal([]byte(appInstall.Env), &envs); err != nil {
|
||||
return err
|
||||
}
|
||||
if out, err := compose.Down(appInstall.GetComposePath()); err != nil {
|
||||
if out != "" {
|
||||
return errors.New(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
busErr error
|
||||
fileOp = files.NewFileOp()
|
||||
envPath = appInstall.GetEnvPath()
|
||||
composePath = appInstall.GetComposePath()
|
||||
confDir = path.Join(appInstall.GetPath(), "conf")
|
||||
backupConfDir = path.Join(appInstall.GetPath(), "conf_bak")
|
||||
fpmConfDir = path.Join(confDir, "php-fpm.conf")
|
||||
phpDir = path.Join(constant.RuntimeDir, runtime.Type, runtime.Name, "php")
|
||||
oldFmContent, _ = fileOp.GetContent(fpmConfDir)
|
||||
)
|
||||
envParams := make(map[string]string, len(envs))
|
||||
handleMap(envs, envParams)
|
||||
envParams["IMAGE_NAME"] = runtime.Image
|
||||
defer func() {
|
||||
if busErr != nil {
|
||||
envParams["IMAGE_NAME"] = oldRuntime.Image
|
||||
_ = env.Write(envParams, envPath)
|
||||
_ = fileOp.WriteFile(composePath, strings.NewReader(appInstall.DockerCompose), 0775)
|
||||
if fileOp.Stat(backupConfDir) {
|
||||
_ = fileOp.DeleteDir(confDir)
|
||||
_ = fileOp.Rename(backupConfDir, confDir)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if busErr = env.Write(envParams, envPath); busErr != nil {
|
||||
return busErr
|
||||
}
|
||||
if busErr = fileOp.WriteFile(composePath, strings.NewReader(appDetail.DockerCompose), 0775); busErr != nil {
|
||||
return busErr
|
||||
}
|
||||
if !req.RetainConfig {
|
||||
if busErr = fileOp.Rename(confDir, backupConfDir); busErr != nil {
|
||||
return busErr
|
||||
}
|
||||
_ = fileOp.CreateDir(confDir, 0755)
|
||||
if busErr = fileOp.CopyFile(path.Join(phpDir, "php-fpm.conf"), confDir); busErr != nil {
|
||||
return busErr
|
||||
}
|
||||
if busErr = fileOp.CopyFile(path.Join(phpDir, "php.ini"), confDir); busErr != nil {
|
||||
_ = fileOp.WriteFile(fpmConfDir, bytes.NewReader(oldFmContent), 0775)
|
||||
return busErr
|
||||
}
|
||||
}
|
||||
if out, err := compose.Up(appInstall.GetComposePath()); err != nil {
|
||||
if out != "" {
|
||||
busErr = errors.New(out)
|
||||
return busErr
|
||||
}
|
||||
busErr = err
|
||||
return busErr
|
||||
}
|
||||
|
||||
_ = fileOp.DeleteDir(backupConfDir)
|
||||
|
||||
appInstall.AppDetailId = runtime.AppDetailID
|
||||
appInstall.AppId = appDetail.AppId
|
||||
appInstall.Version = appDetail.Version
|
||||
appInstall.DockerCompose = appDetail.DockerCompose
|
||||
|
||||
_ = appInstallRepo.Save(context.Background(), &appInstall)
|
||||
website.RuntimeID = req.RuntimeID
|
||||
return websiteRepo.Save(context.Background(), &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
@@ -1690,7 +1809,7 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
backpContent, err := fileOp.GetContent(nginxFull.SiteConfig.Config.FilePath)
|
||||
backupContent, err := fileOp.GetContent(nginxFull.SiteConfig.Config.FilePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -1761,7 +1880,7 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
|
||||
return
|
||||
}
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, nil, &website); err != nil {
|
||||
_ = fileOp.WriteFile(nginxFull.SiteConfig.Config.FilePath, bytes.NewReader(backpContent), 0755)
|
||||
_ = fileOp.WriteFile(nginxFull.SiteConfig.Config.FilePath, bytes.NewReader(backupContent), 0755)
|
||||
return
|
||||
}
|
||||
return
|
||||
@@ -1844,3 +1963,341 @@ func (w WebsiteService) GetAntiLeech(id uint) (*response.NginxAntiLeechRes, erro
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error) {
|
||||
var (
|
||||
website model.Website
|
||||
nginxInstall model.AppInstall
|
||||
oldContent []byte
|
||||
)
|
||||
|
||||
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "redirect")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, 0755)
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.conf", req.Name)
|
||||
includePath := path.Join(includeDir, fileName)
|
||||
backName := fmt.Sprintf("%s.bak", req.Name)
|
||||
backPath := path.Join(includeDir, backName)
|
||||
|
||||
if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) {
|
||||
err = buserr.New(constant.ErrNameIsExist)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
case "edit":
|
||||
_ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), 0755)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
config *components.Config
|
||||
oldPar *parser.Parser
|
||||
)
|
||||
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
config = &components.Config{}
|
||||
case "edit":
|
||||
oldPar, err = parser.NewParser(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config = oldPar.Parse()
|
||||
oldContent, err = fileOp.GetContent(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "delete":
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
_ = fileOp.DeleteFile(backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "disable":
|
||||
_ = fileOp.Rename(includePath, backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "enable":
|
||||
_ = fileOp.Rename(backPath, includePath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
target := req.Target
|
||||
block := &components.Block{}
|
||||
|
||||
switch req.Type {
|
||||
case "path":
|
||||
if req.KeepPath {
|
||||
target = req.Target + "$1"
|
||||
} else {
|
||||
target = req.Target + "?"
|
||||
}
|
||||
redirectKey := "permanent"
|
||||
if req.Redirect == "302" {
|
||||
redirectKey = "redirect"
|
||||
}
|
||||
block = &components.Block{
|
||||
Directives: []components.IDirective{
|
||||
&components.Directive{
|
||||
Name: "rewrite",
|
||||
Parameters: []string{fmt.Sprintf("^%s(.*)", req.Path), target, redirectKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
case "domain":
|
||||
if req.KeepPath {
|
||||
target = req.Target + "$request_uri"
|
||||
}
|
||||
returnBlock := &components.Block{
|
||||
Directives: []components.IDirective{
|
||||
&components.Directive{
|
||||
Name: "return",
|
||||
Parameters: []string{req.Redirect, target},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, domain := range req.Domains {
|
||||
block.Directives = append(block.Directives, &components.Directive{
|
||||
Name: "if",
|
||||
Parameters: []string{"($host", "~", fmt.Sprintf("'^%s')", domain)},
|
||||
Block: returnBlock,
|
||||
})
|
||||
}
|
||||
case "404":
|
||||
if req.KeepPath && !req.RedirectRoot {
|
||||
target = req.Target + "$request_uri"
|
||||
}
|
||||
if req.RedirectRoot {
|
||||
target = "/"
|
||||
} else {
|
||||
if req.KeepPath {
|
||||
target = req.Target + "$request_uri"
|
||||
}
|
||||
}
|
||||
block = &components.Block{
|
||||
Directives: []components.IDirective{
|
||||
&components.Directive{
|
||||
Name: "error_page",
|
||||
Parameters: []string{"404", "=", "@notfound"},
|
||||
},
|
||||
&components.Directive{
|
||||
Name: "location",
|
||||
Parameters: []string{"@notfound"},
|
||||
Block: &components.Block{
|
||||
Directives: []components.IDirective{
|
||||
&components.Directive{
|
||||
Name: "return",
|
||||
Parameters: []string{"301", target},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
config.FilePath = includePath
|
||||
config.Block = block
|
||||
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
|
||||
}
|
||||
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/redirect/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) {
|
||||
var (
|
||||
website model.Website
|
||||
nginxInstall model.AppInstall
|
||||
fileList response.FileInfo
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "redirect")
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
return
|
||||
}
|
||||
fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}})
|
||||
if len(fileList.Items) == 0 {
|
||||
return
|
||||
}
|
||||
var (
|
||||
content []byte
|
||||
config *components.Config
|
||||
)
|
||||
for _, configFile := range fileList.Items {
|
||||
redirectConfig := response.NginxRedirectConfig{
|
||||
WebsiteID: website.ID,
|
||||
}
|
||||
parts := strings.Split(configFile.Name, ".")
|
||||
redirectConfig.Name = parts[0]
|
||||
if parts[1] == "conf" {
|
||||
redirectConfig.Enable = true
|
||||
} else {
|
||||
redirectConfig.Enable = false
|
||||
}
|
||||
redirectConfig.FilePath = configFile.Path
|
||||
content, err = fileOp.GetContent(configFile.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
redirectConfig.Content = string(content)
|
||||
config = parser.NewStringParser(string(content)).Parse()
|
||||
|
||||
dirs := config.GetDirectives()
|
||||
if len(dirs) > 0 {
|
||||
firstName := dirs[0].GetName()
|
||||
switch firstName {
|
||||
case "if":
|
||||
for _, ifDir := range dirs {
|
||||
params := ifDir.GetParameters()
|
||||
if len(params) > 2 && params[0] == "($host" {
|
||||
domain := strings.Trim(strings.Trim(params[2], "'"), "^")
|
||||
redirectConfig.Domains = append(redirectConfig.Domains, domain)
|
||||
if len(redirectConfig.Domains) > 1 {
|
||||
continue
|
||||
}
|
||||
redirectConfig.Type = "domain"
|
||||
}
|
||||
childDirs := ifDir.GetBlock().GetDirectives()
|
||||
for _, dir := range childDirs {
|
||||
if dir.GetName() == "return" {
|
||||
dirParams := dir.GetParameters()
|
||||
if len(dirParams) > 1 {
|
||||
redirectConfig.Redirect = dirParams[0]
|
||||
if strings.HasSuffix(dirParams[1], "$request_uri") {
|
||||
redirectConfig.KeepPath = true
|
||||
redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri")
|
||||
} else {
|
||||
redirectConfig.KeepPath = false
|
||||
redirectConfig.Target = dirParams[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "rewrite":
|
||||
redirectConfig.Type = "path"
|
||||
for _, pathDir := range dirs {
|
||||
if pathDir.GetName() == "rewrite" {
|
||||
params := pathDir.GetParameters()
|
||||
if len(params) > 2 {
|
||||
redirectConfig.Path = strings.Trim(strings.Trim(params[0], "^"), "(.*)")
|
||||
if strings.HasSuffix(params[1], "$1") {
|
||||
redirectConfig.KeepPath = true
|
||||
redirectConfig.Target = strings.TrimSuffix(params[1], "$1")
|
||||
} else {
|
||||
redirectConfig.KeepPath = false
|
||||
redirectConfig.Target = strings.TrimSuffix(params[1], "?")
|
||||
}
|
||||
if params[2] == "permanent" {
|
||||
redirectConfig.Redirect = "301"
|
||||
} else {
|
||||
redirectConfig.Redirect = "302"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "error_page":
|
||||
redirectConfig.Type = "404"
|
||||
for _, errDir := range dirs {
|
||||
if errDir.GetName() == "location" {
|
||||
childDirs := errDir.GetBlock().GetDirectives()
|
||||
for _, dir := range childDirs {
|
||||
if dir.GetName() == "return" {
|
||||
dirParams := dir.GetParameters()
|
||||
if len(dirParams) > 1 {
|
||||
redirectConfig.Redirect = dirParams[0]
|
||||
if strings.HasSuffix(dirParams[1], "$request_uri") {
|
||||
redirectConfig.KeepPath = true
|
||||
redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri")
|
||||
redirectConfig.RedirectRoot = false
|
||||
} else {
|
||||
redirectConfig.KeepPath = false
|
||||
redirectConfig.Target = dirParams[1]
|
||||
redirectConfig.RedirectRoot = redirectConfig.Target == "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res = append(res, redirectConfig)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateRedirectFile(req request.NginxRedirectUpdate) (err error) {
|
||||
var (
|
||||
website model.Website
|
||||
nginxFull dto.NginxFull
|
||||
oldRewriteContent []byte
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxFull, err = getNginxFull(&website)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includePath := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, req.Name)
|
||||
absolutePath := path.Join(nginxFull.Install.GetPath(), includePath)
|
||||
fileOp := files.NewFileOp()
|
||||
oldRewriteContent, err = fileOp.GetContent(absolutePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), 0755)
|
||||
}
|
||||
}()
|
||||
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)
|
||||
}
|
||||
|
@@ -576,6 +576,44 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
return nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName)
|
||||
}
|
||||
|
||||
func changeIPV6(website model.Website, enable bool) error {
|
||||
nginxFull, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
config := nginxFull.SiteConfig.Config
|
||||
server := config.FindServers()[0]
|
||||
listens := server.Listens
|
||||
if enable {
|
||||
for _, listen := range listens {
|
||||
if strings.HasPrefix(listen.Bind, "[::]:") {
|
||||
continue
|
||||
}
|
||||
exist := false
|
||||
ipv6Bind := fmt.Sprintf("[::]:%s", listen.Bind)
|
||||
for _, li := range listens {
|
||||
if li.Bind == ipv6Bind {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
server.UpdateListen(ipv6Bind, false, listen.GetParameters()[1:]...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, listen := range listens {
|
||||
if strings.HasPrefix(listen.Bind, "[::]:") {
|
||||
server.RemoveListenByBind(listen.Bind)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
return nginxCheckAndReload(nginxFull.SiteConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName)
|
||||
}
|
||||
|
||||
func checkIsLinkApp(website model.Website) bool {
|
||||
if website.Type == constant.Deployment {
|
||||
return true
|
||||
|
@@ -61,3 +61,18 @@ func WithMap(Key string, maps map[string]interface{}, err error) BusinessError {
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func WithNameAndErr(Key string, name string, err error) BusinessError {
|
||||
paramMap := map[string]interface{}{}
|
||||
if name != "" {
|
||||
paramMap["name"] = name
|
||||
}
|
||||
if err != nil {
|
||||
paramMap["err"] = err.Error()
|
||||
}
|
||||
return BusinessError{
|
||||
Msg: Key,
|
||||
Map: paramMap,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
8
backend/constant/host_tool.go
Normal file
8
backend/constant/host_tool.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
Supervisord = "supervisord"
|
||||
Supervisor = "supervisor"
|
||||
SupervisorConfigPath = "SupervisorConfigPath"
|
||||
SupervisorServiceName = "SupervisorServiceName"
|
||||
)
|
@@ -44,6 +44,8 @@ ErrNoSuchHost: "Network connection failed"
|
||||
ErrImagePullTimeOut: 'Image pull timeout'
|
||||
ErrContainerNotFound: '{{ .name }} container does not exist'
|
||||
ErrContainerMsg: '{{ .name }} container is abnormal, please check the log on the container page for details'
|
||||
ErrAppBackup: '{{ .name }} application backup failed err {{.err}}'
|
||||
ErrImagePull: '{{ .name }} image pull failed err {{.err}}'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "File can not read"
|
||||
@@ -62,6 +64,7 @@ ErrAppDelete: 'Other Website use this App'
|
||||
ErrGroupIsUsed: 'The group is in use and cannot be deleted'
|
||||
ErrBackupMatch: 'the backup file does not match the current partial data of the website: {{ .detail}}"'
|
||||
ErrBackupExist: 'the backup file corresponds to a portion of the original data that does not exist: {{ .detail}}"'
|
||||
ErrPHPResource: 'The local runtime does not support switching!'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed"
|
||||
@@ -76,7 +79,7 @@ ErrSSLCertificateFormat: 'Certificate file format error, please use pem format'
|
||||
#mysql
|
||||
ErrUserIsExist: "The current user already exists. Please enter a new user"
|
||||
ErrDatabaseIsExist: "The current database already exists. Please enter a new database"
|
||||
ErrExecTimeOut: "SQL execution timed out, please check the {{ .detail }} container"
|
||||
ErrExecTimeOut: "SQL execution timed out, please check the database"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "The recovery file type does not match the current persistence mode. Modify the file type and try again"
|
||||
@@ -95,5 +98,12 @@ ErrDelWithWebsite: "The operating environment has been associated with a website
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "The backup account is already being used in a cronjob and cannot be deleted."
|
||||
ErrOSSConn: "Unable to successfully request the latest version. Please check if the server can connect to the external network environment."
|
||||
|
||||
ErrOSSConn: "Unable to successfully request the latest version. Please check if the server can connect to the external network environment."
|
||||
#tool
|
||||
ErrConfigNotFound: "Configuration file does not exist"
|
||||
ErrConfigParse: "Configuration file format error"
|
||||
ErrConfigIsNull: "The configuration file is not allowed to be empty"
|
||||
ErrConfigDirNotFound: "The running directory does not exist"
|
||||
ErrConfigAlreadyExist: "A configuration file with the same name already exists"
|
||||
ErrUserFindErr: "Failed to find user {{ .name }} {{ .err }}"
|
@@ -44,6 +44,8 @@ ErrNoSuchHost: "網路連接失敗"
|
||||
ErrImagePullTimeOut: "鏡像拉取超時"
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌'
|
||||
ErrAppBackup: '{{ .name }} 應用備份失敗 err {{.err}}'
|
||||
ErrImagePull: '{{ .name }} 鏡像拉取失敗 err {{.err}}'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
@@ -62,6 +64,7 @@ ErrAppDelete: '其他網站使用此應用,無法刪除'
|
||||
ErrGroupIsUsed: '分組正在使用中,無法刪除'
|
||||
ErrBackupMatch: '該備份文件與當前網站部分數據不匹配: {{ .detail}}"'
|
||||
ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}"'
|
||||
ErrPHPResource: '本地運行環境不支持切換!'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "證書正在被網站使用,無法刪除"
|
||||
@@ -76,7 +79,7 @@ ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
|
||||
#mysql
|
||||
ErrUserIsExist: "當前用戶已存在,請重新輸入"
|
||||
ErrDatabaseIsExist: "當前資料庫已存在,請重新輸入"
|
||||
ErrExecTimeOut: "SQL 執行超時,請檢查{{ .detail }}容器"
|
||||
ErrExecTimeOut: "SQL 執行超時,請檢查數據庫"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後重試"
|
||||
@@ -95,5 +98,12 @@ ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||
|
||||
ErrOSSConn: "無法成功請求最新版本,請檢查伺服器是否能夠連接到外部網絡環境。"
|
||||
|
||||
#tool
|
||||
ErrConfigNotFound: "配置文件不存在"
|
||||
ErrConfigParse: "配置文件格式有誤"
|
||||
ErrConfigIsNull: "配置文件不允許為空"
|
||||
ErrConfigDirNotFound: "運行目錄不存在"
|
||||
ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用戶 {{ .name }} 查找失敗 {{ .err }}"
|
||||
|
@@ -44,6 +44,8 @@ ErrNoSuchHost: "网络连接失败"
|
||||
ErrImagePullTimeOut: '镜像拉取超时'
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
|
||||
ErrAppBackup: '{{ .name }} 应用备份失败 err {{.err}}'
|
||||
ErrImagePull: '镜像拉取失败 {{.err}}'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
@@ -62,6 +64,7 @@ ErrAppDelete: '其他网站使用此应用,无法删除'
|
||||
ErrGroupIsUsed: '分组正在使用中,无法删除'
|
||||
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail}}"'
|
||||
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}"'
|
||||
ErrPHPResource: '本地运行环境不支持切换!'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "证书正在被网站使用,无法删除"
|
||||
@@ -76,7 +79,7 @@ ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
|
||||
#mysql
|
||||
ErrUserIsExist: "当前用户已存在,请重新输入"
|
||||
ErrDatabaseIsExist: "当前数据库已存在,请重新输入"
|
||||
ErrExecTimeOut: "SQL 执行超时,请检查{{ .detail }}容器"
|
||||
ErrExecTimeOut: "SQL 执行超时,请检查数据库"
|
||||
|
||||
#redis
|
||||
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
|
||||
@@ -95,5 +98,12 @@ ErrDelWithWebsite: "运行环境已经关联网站,无法删除"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
|
||||
ErrOSSConn: "无法成功请求最新版本,请检查服务器是否能够连接到外部网络环境。"
|
||||
|
||||
#tool
|
||||
ErrConfigNotFound: "配置文件不存在"
|
||||
ErrConfigParse: "配置文件格式有误"
|
||||
ErrConfigIsNull: "配置文件不允许为空"
|
||||
ErrConfigDirNotFound: "运行目录不存在"
|
||||
ErrConfigAlreadyExist: "已存在同名配置文件"
|
||||
ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}"
|
||||
|
@@ -35,6 +35,9 @@ func Init() {
|
||||
migrations.AddMfaInterval,
|
||||
migrations.UpdateAppDetail,
|
||||
migrations.EncryptHostPassword,
|
||||
migrations.AddRemoteDB,
|
||||
migrations.UpdateRedisParam,
|
||||
migrations.UpdateCronjobWithDb,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -125,7 +127,7 @@ var AddTableSetting = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.Setting{Key: "MonitorStatus", Value: "enable"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "MonitorStoreDays", Value: "30"}).Error; err != nil {
|
||||
if err := tx.Create(&model.Setting{Key: "MonitorStoreDays", Value: "7"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -352,7 +354,7 @@ var AddBindAndAllowIPs = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.Setting{Key: "NtpSite", Value: "pool.ntp.org"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "MonitorInterval", Value: "1"}).Error; err != nil {
|
||||
if err := tx.Create(&model.Setting{Key: "MonitorInterval", Value: "5"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -479,3 +481,92 @@ var EncryptHostPassword = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddRemoteDB = &gormigrate.Migration{
|
||||
ID: "20230724-add-remote-db",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.RemoteDB{}, &model.DatabaseMysql{}); err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
app model.App
|
||||
appInstall model.AppInstall
|
||||
)
|
||||
if err := global.DB.Where("key = ?", "mysql").First(&app).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
envMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(appInstall.Env), &envMap); err != nil {
|
||||
return err
|
||||
}
|
||||
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
|
||||
if !ok {
|
||||
return errors.New("error password in app env")
|
||||
}
|
||||
if err := tx.Create(&model.RemoteDB{
|
||||
Name: "local",
|
||||
Type: "mysql",
|
||||
Version: appInstall.Version,
|
||||
From: "local",
|
||||
Address: "127.0.0.1",
|
||||
Username: "root",
|
||||
Password: password,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateRedisParam = &gormigrate.Migration{
|
||||
ID: "20230804-update-redis-param",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var (
|
||||
app model.App
|
||||
appInstall model.AppInstall
|
||||
)
|
||||
if err := global.DB.Where("key = ?", "redis").First(&app).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
appInstall.Param = strings.ReplaceAll(appInstall.Param, "PANEL_DB_ROOT_PASSWORD", "PANEL_REDIS_ROOT_PASSWORD")
|
||||
appInstall.DockerCompose = strings.ReplaceAll(appInstall.DockerCompose, "PANEL_DB_ROOT_PASSWORD", "PANEL_REDIS_ROOT_PASSWORD")
|
||||
appInstall.Env = strings.ReplaceAll(appInstall.Env, "PANEL_DB_ROOT_PASSWORD", "PANEL_REDIS_ROOT_PASSWORD")
|
||||
if err := tx.Model(&model.AppInstall{}).Where("id = ?", appInstall.ID).Updates(appInstall).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateCronjobWithDb = &gormigrate.Migration{
|
||||
ID: "20230809-update-cronjob-with-db",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var cronjobs []model.Cronjob
|
||||
if err := global.DB.Where("type = ? AND db_name != ?", "database", "all").Find(&cronjobs).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, job := range cronjobs {
|
||||
var db model.DatabaseMysql
|
||||
if err := global.DB.Where("name = ?", job.DBName).First(&db).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
if err := tx.Model(&model.Cronjob{}).
|
||||
Where("id = ?", job.ID).
|
||||
Updates(map[string]interface{}{"db_name": db.ID}).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@@ -47,9 +47,6 @@ func Init() {
|
||||
password = loadParams("ORIGINAL_PASSWORD")
|
||||
entrance = loadParams("ORIGINAL_ENTRANCE")
|
||||
|
||||
if strings.HasSuffix(baseDir, "/") {
|
||||
baseDir = baseDir[:strings.LastIndex(baseDir, "/")]
|
||||
}
|
||||
reader := bytes.NewReader(conf.AppYaml)
|
||||
if err := v.ReadConfig(reader); err != nil {
|
||||
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
||||
@@ -88,12 +85,12 @@ func Init() {
|
||||
global.CONF = serverConfig
|
||||
global.CONF.System.BaseDir = baseDir
|
||||
global.CONF.System.IsDemo = v.GetBool("system.is_demo")
|
||||
global.CONF.System.DataDir = global.CONF.System.BaseDir + "/1panel"
|
||||
global.CONF.System.Cache = global.CONF.System.DataDir + "/cache"
|
||||
global.CONF.System.Backup = global.CONF.System.DataDir + "/backup"
|
||||
global.CONF.System.DbPath = global.CONF.System.DataDir + "/db"
|
||||
global.CONF.System.LogPath = global.CONF.System.DataDir + "/log"
|
||||
global.CONF.System.TmpDir = global.CONF.System.DataDir + "/tmp"
|
||||
global.CONF.System.DataDir = path.Join(global.CONF.System.BaseDir, "1panel")
|
||||
global.CONF.System.Cache = path.Join(global.CONF.System.DataDir, "cache")
|
||||
global.CONF.System.Backup = path.Join(global.CONF.System.DataDir, "backup")
|
||||
global.CONF.System.DbPath = path.Join(global.CONF.System.DataDir, "db")
|
||||
global.CONF.System.LogPath = path.Join(global.CONF.System.DataDir, "log")
|
||||
global.CONF.System.TmpDir = path.Join(global.CONF.System.DataDir, "tmp")
|
||||
global.CONF.System.Port = port
|
||||
global.CONF.System.Version = version
|
||||
global.CONF.System.Username = username
|
||||
|
@@ -17,5 +17,6 @@ func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) {
|
||||
baseRouter.GET("/issafety", baseApi.CheckIsSafety)
|
||||
baseRouter.POST("/logout", baseApi.LogOut)
|
||||
baseRouter.GET("/demo", baseApi.CheckIsDemo)
|
||||
baseRouter.GET("/language", baseApi.GetLanguage)
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
||||
baRouter.GET("/search/log", baseApi.ContainerLogs)
|
||||
baRouter.GET("/limit", baseApi.LoadResouceLimit)
|
||||
baRouter.POST("/clean/log", baseApi.CleanContainerLog)
|
||||
baRouter.POST("/load/log", baseApi.LoadContainerLog)
|
||||
baRouter.POST("/inspect", baseApi.Inspect)
|
||||
baRouter.POST("/operate", baseApi.ContainerOperation)
|
||||
baRouter.POST("/prune", baseApi.ContainerPrune)
|
||||
|
@@ -24,6 +24,7 @@ func (s *CronjobRouter) InitCronjobRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/download", baseApi.TargetDownload)
|
||||
cmdRouter.POST("/search", baseApi.SearchCronjob)
|
||||
cmdRouter.POST("/search/records", baseApi.SearchJobRecords)
|
||||
cmdRouter.POST("/records/log", baseApi.LoadRecordLog)
|
||||
cmdRouter.POST("/records/clean", baseApi.CleanRecord)
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
cmdRouter.POST("", baseApi.CreateMysql)
|
||||
cmdRouter.GET("load/:from", baseApi.LoadDBFromRemote)
|
||||
cmdRouter.POST("/change/access", baseApi.ChangeMysqlAccess)
|
||||
cmdRouter.POST("/change/password", baseApi.ChangeMysqlPassword)
|
||||
cmdRouter.POST("/del/check", baseApi.DeleteCheckMysql)
|
||||
@@ -25,6 +26,7 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/variables/update", baseApi.UpdateMysqlVariables)
|
||||
cmdRouter.POST("/conffile/update", baseApi.UpdateMysqlConfByFile)
|
||||
cmdRouter.POST("/search", baseApi.SearchMysql)
|
||||
cmdRouter.POST("/load/file", baseApi.LoadDatabaseFile)
|
||||
cmdRouter.GET("/variables", baseApi.LoadVariables)
|
||||
cmdRouter.GET("/status", baseApi.LoadStatus)
|
||||
cmdRouter.GET("/baseinfo", baseApi.LoadBaseinfo)
|
||||
@@ -40,5 +42,13 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
||||
cmdRouter.POST("/redis/conffile/update", baseApi.UpdateRedisConfByFile)
|
||||
cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf)
|
||||
|
||||
cmdRouter.POST("/remote/check", baseApi.CheckeRemoteDB)
|
||||
cmdRouter.POST("/remote", baseApi.CreateRemoteDB)
|
||||
cmdRouter.GET("/remote/:name", baseApi.GetRemoteDB)
|
||||
cmdRouter.GET("/remote/list/:type", baseApi.ListRemoteDB)
|
||||
cmdRouter.POST("/remote/update", baseApi.UpdateRemoteDB)
|
||||
cmdRouter.POST("/remote/search", baseApi.SearchRemoteDB)
|
||||
cmdRouter.POST("/remote/del", baseApi.DeleteRemoteDB)
|
||||
}
|
||||
}
|
||||
|
@@ -33,12 +33,9 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/wget", baseApi.WgetFile)
|
||||
fileRouter.POST("/move", baseApi.MoveFile)
|
||||
fileRouter.GET("/download", baseApi.Download)
|
||||
fileRouter.POST("/download/bypath", baseApi.DownloadFile)
|
||||
fileRouter.POST("/chunkdownload", baseApi.DownloadChunkFiles)
|
||||
fileRouter.POST("/size", baseApi.Size)
|
||||
fileRouter.GET("/ws", baseApi.Ws)
|
||||
fileRouter.GET("/keys", baseApi.Keys)
|
||||
fileRouter.POST("/loadfile", baseApi.LoadFromFile)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule)
|
||||
hostRouter.POST("/firewall/update/addr", baseApi.UpdateAddrRule)
|
||||
|
||||
hostRouter.GET("/ssh/conf", baseApi.LoadSSHConf)
|
||||
hostRouter.POST("/ssh/search", baseApi.GetSSHInfo)
|
||||
hostRouter.POST("/ssh/update", baseApi.UpdateSSH)
|
||||
hostRouter.POST("/ssh/generate", baseApi.GenerateSSH)
|
||||
@@ -47,5 +48,14 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/command/del", baseApi.DeleteCommand)
|
||||
hostRouter.POST("/command/search", baseApi.SearchCommand)
|
||||
hostRouter.POST("/command/update", baseApi.UpdateCommand)
|
||||
|
||||
hostRouter.POST("/tool", baseApi.GetToolStatus)
|
||||
hostRouter.POST("/tool/init", baseApi.InitToolConfig)
|
||||
hostRouter.POST("/tool/operate", baseApi.OperateTool)
|
||||
hostRouter.POST("/tool/config", baseApi.OperateToolConfig)
|
||||
hostRouter.POST("/tool/log", baseApi.GetToolLog)
|
||||
hostRouter.POST("/tool/supervisor/process", baseApi.OperateProcess)
|
||||
hostRouter.GET("/tool/supervisor/process", baseApi.GetProcess)
|
||||
hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile)
|
||||
}
|
||||
}
|
||||
|
@@ -17,5 +17,7 @@ func (s *LogRouter) InitLogRouter(Router *gin.RouterGroup) {
|
||||
operationRouter.POST("/login", baseApi.GetLoginLogs)
|
||||
operationRouter.POST("/operation", baseApi.GetOperationLogs)
|
||||
operationRouter.POST("/clean", baseApi.CleanLogs)
|
||||
operationRouter.GET("/system", baseApi.GetSystemLogs)
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
||||
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.GET("/time/option", baseApi.LoadTimeZone)
|
||||
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
||||
|
@@ -41,10 +41,12 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
|
||||
|
||||
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)
|
||||
groupRouter.POST("/php/version", baseApi.ChangePHPVersion)
|
||||
|
||||
groupRouter.POST("/rewrite", baseApi.GetRewriteConfig)
|
||||
groupRouter.POST("/rewrite/update", baseApi.UpdateRewriteConfig)
|
||||
@@ -61,5 +63,9 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
|
||||
|
||||
groupRouter.POST("/leech", baseApi.GetAntiLeech)
|
||||
groupRouter.POST("/leech/update", baseApi.UpdateAntiLeech)
|
||||
|
||||
groupRouter.POST("/redirect/update", baseApi.UpdateRedirectConfig)
|
||||
groupRouter.POST("/redirect", baseApi.GetRedirectConfig)
|
||||
groupRouter.POST("/redirect/file", baseApi.UpdateRedirectConfigFile)
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/init/app"
|
||||
@@ -58,11 +59,11 @@ func Start() {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
certificate, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
|
||||
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
key, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
|
||||
key, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -127,7 +127,7 @@ func (cos *cosClient) GetBucket() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cos cosClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
func (cos cosClient) ListObjects(prefix string) ([]string, error) {
|
||||
client, err := cos.newClientWithBucket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -137,7 +137,7 @@ func (cos cosClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []interface{}
|
||||
var result []string
|
||||
for _, item := range datas.Contents {
|
||||
result = append(result, item.Key)
|
||||
}
|
||||
|
@@ -119,13 +119,13 @@ func (kodo *kodoClient) GetBucket() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (kodo kodoClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
func (kodo kodoClient) ListObjects(prefix string) ([]string, error) {
|
||||
bucket, err := kodo.GetBucket()
|
||||
if err != nil {
|
||||
return nil, constant.ErrInvalidParams
|
||||
}
|
||||
|
||||
var result []interface{}
|
||||
var result []string
|
||||
marker := ""
|
||||
for {
|
||||
entries, _, nextMarker, hashNext, err := kodo.client.ListFiles(bucket, prefix, "", marker, 1000)
|
||||
|
@@ -164,7 +164,7 @@ func (minIo *minIoClient) GetBucket() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (minIo minIoClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
func (minIo minIoClient) ListObjects(prefix string) ([]string, error) {
|
||||
bucket, err := minIo.GetBucket()
|
||||
if err != nil {
|
||||
return nil, constant.ErrInvalidParams
|
||||
@@ -174,7 +174,7 @@ func (minIo minIoClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
Prefix: prefix,
|
||||
}
|
||||
|
||||
var result []interface{}
|
||||
var result []string
|
||||
for object := range minIo.client.ListObjects(context.Background(), bucket, opts) {
|
||||
if object.Err != nil {
|
||||
continue
|
||||
|
@@ -196,7 +196,7 @@ func (onedrive oneDriveClient) Download(src, target string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
func (onedrive *oneDriveClient) ListObjects(prefix string) ([]string, error) {
|
||||
prefix = "/" + strings.TrimPrefix(prefix, "/")
|
||||
folderID, err := onedrive.loadIDByPath(prefix)
|
||||
if err != nil {
|
||||
@@ -211,11 +211,8 @@ func (onedrive *oneDriveClient) ListObjects(prefix string) ([]interface{}, error
|
||||
if err := onedrive.client.Do(context.Background(), req, false, &driveItems); err != nil {
|
||||
return nil, fmt.Errorf("do request for list failed, err: %v", err)
|
||||
}
|
||||
for _, item := range driveItems.DriveItems {
|
||||
return nil, fmt.Errorf("id: %v, name: %s \n", item.Id, item.Name)
|
||||
}
|
||||
|
||||
var itemList []interface{}
|
||||
var itemList []string
|
||||
for _, item := range driveItems.DriveItems {
|
||||
itemList = append(itemList, item.Name)
|
||||
}
|
||||
|
@@ -116,7 +116,7 @@ func (oss *ossClient) GetBucket() (*osssdk.Bucket, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (oss *ossClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
func (oss *ossClient) ListObjects(prefix string) ([]string, error) {
|
||||
bucket, err := oss.GetBucket()
|
||||
if err != nil {
|
||||
return nil, constant.ErrInvalidParams
|
||||
@@ -125,7 +125,7 @@ func (oss *ossClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result []interface{}
|
||||
var result []string
|
||||
for _, obj := range lor.Objects {
|
||||
result = append(result, obj.Key)
|
||||
}
|
||||
|
@@ -184,13 +184,13 @@ func (s3C *s3Client) getBucket() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s3C *s3Client) ListObjects(prefix string) ([]interface{}, error) {
|
||||
func (s3C *s3Client) ListObjects(prefix string) ([]string, error) {
|
||||
bucket, err := s3C.getBucket()
|
||||
if err != nil {
|
||||
return nil, constant.ErrInvalidParams
|
||||
}
|
||||
svc := s3.New(&s3C.Sess)
|
||||
var result []interface{}
|
||||
var result []string
|
||||
if err := svc.ListObjectsPages(&s3.ListObjectsInput{
|
||||
Bucket: &bucket,
|
||||
Prefix: &prefix,
|
||||
|
@@ -218,7 +218,7 @@ func (s sftpClient) getBucket() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s sftpClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
func (s sftpClient) ListObjects(prefix string) ([]string, error) {
|
||||
bucket, err := s.getBucket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -236,7 +236,7 @@ func (s sftpClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result []interface{}
|
||||
var result []string
|
||||
for _, file := range files {
|
||||
result = append(result, file.Name())
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
type CloudStorageClient interface {
|
||||
ListBuckets() ([]interface{}, error)
|
||||
ListObjects(prefix string) ([]interface{}, error)
|
||||
ListObjects(prefix string) ([]string, error)
|
||||
Exist(path string) (bool, error)
|
||||
Delete(path string) (bool, error)
|
||||
Upload(src, target string) (bool, error)
|
||||
|
@@ -168,3 +168,8 @@ func SudoHandleCmd() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Which(name string) bool {
|
||||
_, err := exec.LookPath(name)
|
||||
return err == nil
|
||||
}
|
||||
|
@@ -4,11 +4,7 @@ import (
|
||||
"context"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/joho/godotenv"
|
||||
"path"
|
||||
"regexp"
|
||||
@@ -21,24 +17,6 @@ type ComposeService struct {
|
||||
project *types.Project
|
||||
}
|
||||
|
||||
func NewComposeService(ops ...command.DockerCliOption) (*ComposeService, error) {
|
||||
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, command.WithAPIClient(apiClient), command.WithDefaultContextStoreConfig())
|
||||
cli, err := command.NewDockerCli(ops...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cliOp := flags.NewClientOptions()
|
||||
if err := cli.Initialize(cliOp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service := compose.NewComposeService(cli)
|
||||
return &ComposeService{service, nil}, nil
|
||||
}
|
||||
|
||||
func (s *ComposeService) SetProject(project *types.Project) {
|
||||
s.project = project
|
||||
for i, s := range project.Services {
|
||||
|
@@ -72,6 +72,22 @@ func (c Client) DeleteImage(imageID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) PullImage(imageName string, force bool) error {
|
||||
if !force {
|
||||
exist, err := c.CheckImageExist(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if _, err := c.cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) GetImageIDByName(imageName string) (string, error) {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("reference", imageName)
|
||||
@@ -87,6 +103,18 @@ func (c Client) GetImageIDByName(imageName string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (c Client) CheckImageExist(imageName string) (bool, error) {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("reference", imageName)
|
||||
list, err := c.cli.ImageList(context.Background(), types.ImageListOptions{
|
||||
Filters: filter,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(list) > 0, nil
|
||||
}
|
||||
|
||||
func (c Client) NetworkExist(name string) bool {
|
||||
var options types.NetworkListOptions
|
||||
options.Filters = filters.NewArgs(filters.Arg("name", name))
|
||||
|
@@ -107,8 +107,10 @@ func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, tot
|
||||
if err = cmd.Start(); err != nil {
|
||||
return
|
||||
}
|
||||
defer cmd.Wait()
|
||||
defer cmd.Process.Kill()
|
||||
defer func() {
|
||||
_ = cmd.Wait()
|
||||
_ = cmd.Process.Kill()
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(output)
|
||||
for scanner.Scan() {
|
||||
|
@@ -3,6 +3,7 @@ package client
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -60,39 +61,48 @@ func (f *Firewall) Reload() error {
|
||||
}
|
||||
|
||||
func (f *Firewall) ListPort() ([]FireInfo, error) {
|
||||
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-ports")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ports := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ")
|
||||
var wg sync.WaitGroup
|
||||
var datas []FireInfo
|
||||
for _, port := range ports {
|
||||
if len(port) == 0 {
|
||||
continue
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-ports")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var itemPort FireInfo
|
||||
if strings.Contains(port, "/") {
|
||||
itemPort.Port = strings.Split(port, "/")[0]
|
||||
itemPort.Protocol = strings.Split(port, "/")[1]
|
||||
ports := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ")
|
||||
for _, port := range ports {
|
||||
if len(port) == 0 {
|
||||
continue
|
||||
}
|
||||
var itemPort FireInfo
|
||||
if strings.Contains(port, "/") {
|
||||
itemPort.Port = strings.Split(port, "/")[0]
|
||||
itemPort.Protocol = strings.Split(port, "/")[1]
|
||||
}
|
||||
itemPort.Strategy = "accept"
|
||||
datas = append(datas, itemPort)
|
||||
}
|
||||
itemPort.Strategy = "accept"
|
||||
datas = append(datas, itemPort)
|
||||
}
|
||||
}()
|
||||
|
||||
stdout1, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules := strings.Split(stdout1, "\n")
|
||||
for _, rule := range rules {
|
||||
if len(rule) == 0 {
|
||||
continue
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
stdout1, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
itemRule := f.loadInfo(rule)
|
||||
if len(itemRule.Port) != 0 && itemRule.Family == "ipv4" {
|
||||
datas = append(datas, itemRule)
|
||||
rules := strings.Split(stdout1, "\n")
|
||||
for _, rule := range rules {
|
||||
if len(rule) == 0 {
|
||||
continue
|
||||
}
|
||||
itemRule := f.loadInfo(rule)
|
||||
if len(itemRule.Port) != 0 && itemRule.Family == "ipv4" {
|
||||
datas = append(datas, itemRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
|
36
backend/utils/ini_conf/ini.go
Normal file
36
backend/utils/ini_conf/ini.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package ini_conf
|
||||
|
||||
import "gopkg.in/ini.v1"
|
||||
|
||||
func GetIniValue(filePath, Group, Key string) (string, error) {
|
||||
cfg, err := ini.Load(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
service, err := cfg.GetSection(Group)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
startKey, err := service.GetKey(Key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return startKey.Value(), nil
|
||||
}
|
||||
|
||||
func SetIniValue(filePath, Group, Key, value string) error {
|
||||
cfg, err := ini.Load(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service, err := cfg.GetSection(Group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKey := service.Key(Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKey.SetValue(value)
|
||||
return cfg.SaveTo(filePath)
|
||||
}
|
61
backend/utils/mysql/client.go
Normal file
61
backend/utils/mysql/client.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
|
||||
)
|
||||
|
||||
type MysqlClient interface {
|
||||
Create(info client.CreateInfo) error
|
||||
Delete(info client.DeleteInfo) error
|
||||
|
||||
ChangePassword(info client.PasswordChangeInfo) error
|
||||
ChangeAccess(info client.AccessChangeInfo) error
|
||||
|
||||
Backup(info client.BackupInfo) error
|
||||
Recover(info client.RecoverInfo) error
|
||||
|
||||
SyncDB(version string) ([]client.SyncDBInfo, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
|
||||
if conn.From == "local" {
|
||||
if cmd.CheckIllegal(conn.Address, conn.Username, conn.Password) {
|
||||
return nil, buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
connArgs := []string{"exec", conn.Address, "mysql", "-u" + conn.Username, "-p" + conn.Password, "-e"}
|
||||
return client.NewLocal(connArgs, conn.Address, conn.Password, conn.From), nil
|
||||
}
|
||||
|
||||
connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8", conn.Username, conn.Password, conn.Address, conn.Port)
|
||||
db, err := sql.Open("mysql", connArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
|
||||
return client.NewRemote(client.Remote{
|
||||
Client: db,
|
||||
From: conn.From,
|
||||
User: conn.Username,
|
||||
Password: conn.Password,
|
||||
Address: conn.Address,
|
||||
Port: conn.Port,
|
||||
}), nil
|
||||
}
|
87
backend/utils/mysql/client/info.go
Normal file
87
backend/utils/mysql/client/info.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package client
|
||||
|
||||
type DBInfo struct {
|
||||
From string `json:"from"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Username string `json:"userName"`
|
||||
Password string `json:"password"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type CreateInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
Version string `json:"version"`
|
||||
Username string `json:"userName"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type DeleteInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Username string `json:"userName"`
|
||||
Permission string `json:"permission"`
|
||||
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type PasswordChangeInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Username string `json:"userName"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type AccessChangeInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Username string `json:"userName"`
|
||||
Password string `json:"password"`
|
||||
OldPermission string `json:"oldPermission"`
|
||||
Permission string `json:"permission"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type BackupInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
TargetDir string `json:"targetDir"`
|
||||
FileName string `json:"fileName"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type RecoverInfo struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
SourceFile string `json:"sourceFile"`
|
||||
|
||||
Timeout uint `json:"timeout"` // second
|
||||
}
|
||||
|
||||
type SyncDBInfo struct {
|
||||
Name string `json:"name"`
|
||||
From string `json:"from"`
|
||||
MysqlName string `json:"mysqlName"`
|
||||
Format string `json:"format"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
|
||||
var formatMap = map[string]string{
|
||||
"utf8": "utf8_general_ci",
|
||||
"utf8mb4": "utf8mb4_general_ci",
|
||||
"gbk": "gbk_chinese_ci",
|
||||
"big5": "big5_chinese_ci",
|
||||
}
|
368
backend/utils/mysql/client/local.go
Normal file
368
backend/utils/mysql/client/local.go
Normal file
@@ -0,0 +1,368 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
)
|
||||
|
||||
type Local struct {
|
||||
PrefixCommand []string
|
||||
From string
|
||||
Password string
|
||||
ContainerName string
|
||||
}
|
||||
|
||||
func NewLocal(command []string, containerName, password, from string) *Local {
|
||||
return &Local{PrefixCommand: command, ContainerName: containerName, Password: password, From: from}
|
||||
}
|
||||
|
||||
func (r *Local) Create(info CreateInfo) error {
|
||||
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", info.Name, info.Format, formatMap[info.Format])
|
||||
if err := r.ExecSQL(createSql, info.Timeout); err != nil {
|
||||
if strings.Contains(err.Error(), "ERROR 1007") {
|
||||
return buserr.New(constant.ErrDatabaseIsExist)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.CreateUser(info); err != nil {
|
||||
_ = r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Local) CreateUser(info CreateInfo) error {
|
||||
var userlist []string
|
||||
if strings.Contains(info.Permission, ",") {
|
||||
ips := strings.Split(info.Permission, ",")
|
||||
for _, ip := range ips {
|
||||
if len(ip) != 0 {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
|
||||
}
|
||||
|
||||
for _, user := range userlist {
|
||||
if err := r.ExecSQL(fmt.Sprintf("create user %s identified by '%s';", user, info.Password), info.Timeout); err != nil {
|
||||
if strings.Contains(err.Error(), "ERROR 1396") {
|
||||
return buserr.New(constant.ErrUserIsExist)
|
||||
}
|
||||
_ = r.Delete(DeleteInfo{
|
||||
Name: info.Name,
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.Permission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300})
|
||||
return err
|
||||
}
|
||||
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", info.Name, user)
|
||||
if info.Name == "*" {
|
||||
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
|
||||
}
|
||||
if strings.HasPrefix(info.Version, "5.7") || strings.HasPrefix(info.Version, "5.6") {
|
||||
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, info.Password)
|
||||
}
|
||||
if err := r.ExecSQL(grantStr, info.Timeout); err != nil {
|
||||
_ = r.Delete(DeleteInfo{
|
||||
Name: info.Name,
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.Permission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300})
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Local) Delete(info DeleteInfo) error {
|
||||
var userlist []string
|
||||
if strings.Contains(info.Permission, ",") {
|
||||
ips := strings.Split(info.Permission, ",")
|
||||
for _, ip := range ips {
|
||||
if len(ip) != 0 {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
|
||||
}
|
||||
|
||||
for _, user := range userlist {
|
||||
if strings.HasPrefix(info.Version, "5.6") {
|
||||
if err := r.ExecSQL(fmt.Sprintf("drop user %s", user), info.Timeout); err != nil && !info.ForceDelete {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := r.ExecSQL(fmt.Sprintf("drop user if exists %s", user), info.Timeout); err != nil && !info.ForceDelete {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(info.Name) != 0 {
|
||||
if err := r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout); err != nil && !info.ForceDelete {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !info.ForceDelete {
|
||||
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Local) ChangePassword(info PasswordChangeInfo) error {
|
||||
if info.Username != "root" {
|
||||
var userlist []string
|
||||
if strings.Contains(info.Permission, ",") {
|
||||
ips := strings.Split(info.Permission, ",")
|
||||
for _, ip := range ips {
|
||||
if len(ip) != 0 {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
|
||||
}
|
||||
|
||||
for _, user := range userlist {
|
||||
passwordChangeSql := fmt.Sprintf("set password for %s = password('%s')", user, info.Password)
|
||||
if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") {
|
||||
passwordChangeSql = fmt.Sprintf("ALTER USER %s IDENTIFIED WITH mysql_native_password BY '%s';", user, info.Password)
|
||||
}
|
||||
if err := r.ExecSQL(passwordChangeSql, info.Timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
hosts, err := r.ExecSQLForRows("select host from mysql.user where user='root';", info.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, host := range hosts {
|
||||
if host == "%" || host == "localhost" {
|
||||
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Password)
|
||||
if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") {
|
||||
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Password)
|
||||
}
|
||||
if err := r.ExecSQL(passwordRootChangeCMD, info.Timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Local) ChangeAccess(info AccessChangeInfo) error {
|
||||
if info.Username == "root" {
|
||||
info.OldPermission = "%"
|
||||
info.Name = "*"
|
||||
info.Password = r.Password
|
||||
}
|
||||
if info.Permission != info.OldPermission {
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: info.Name,
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Password: info.Password,
|
||||
Permission: info.Permission,
|
||||
Timeout: info.Timeout,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.ExecSQL("flush privileges", 300); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Local) Backup(info BackupInfo) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(info.TargetDir) {
|
||||
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
||||
}
|
||||
}
|
||||
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)
|
||||
cmd := exec.Command("docker", "exec", r.ContainerName, "mysqldump", "-uroot", "-p"+r.Password, info.Name)
|
||||
gzipCmd := exec.Command("gzip", "-cf")
|
||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
||||
gzipCmd.Stdout = outfile
|
||||
_ = gzipCmd.Start()
|
||||
_ = cmd.Run()
|
||||
_ = gzipCmd.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Local) Recover(info RecoverInfo) error {
|
||||
fi, _ := os.Open(info.SourceFile)
|
||||
defer fi.Close()
|
||||
cmd := exec.Command("docker", "exec", "-i", r.ContainerName, "mysql", "-uroot", "-p"+r.Password, info.Name)
|
||||
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
|
||||
}
|
||||
|
||||
func (r *Local) SyncDB(version string) ([]SyncDBInfo, error) {
|
||||
var datas []SyncDBInfo
|
||||
lines, err := r.ExecSQLForRows("SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA", 300)
|
||||
if err != nil {
|
||||
return datas, err
|
||||
}
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == "SCHEMA_NAME" || parts[0] == "information_schema" || parts[0] == "mysql" || parts[0] == "performance_schema" || parts[0] == "sys" {
|
||||
continue
|
||||
}
|
||||
dataItem := SyncDBInfo{
|
||||
Name: parts[0],
|
||||
From: r.From,
|
||||
Format: parts[1],
|
||||
}
|
||||
userLines, err := r.ExecSQLForRows(fmt.Sprintf("SELECT USER,HOST FROM mysql.DB WHERE DB = '%s'", parts[0]), 300)
|
||||
if err != nil {
|
||||
return datas, err
|
||||
}
|
||||
|
||||
var permissionItem []string
|
||||
isLocal := true
|
||||
i := 0
|
||||
for _, userline := range userLines {
|
||||
userparts := strings.Fields(userline)
|
||||
if len(userparts) != 2 {
|
||||
continue
|
||||
}
|
||||
if userparts[0] == "root" {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
dataItem.Username = userparts[0]
|
||||
}
|
||||
dataItem.Username = userparts[0]
|
||||
if dataItem.Username == userparts[0] && userparts[1] == "%" {
|
||||
isLocal = false
|
||||
dataItem.Permission = "%"
|
||||
} else if dataItem.Username == userparts[0] && userparts[1] != "localhost" {
|
||||
isLocal = false
|
||||
permissionItem = append(permissionItem, userparts[1])
|
||||
}
|
||||
}
|
||||
if len(dataItem.Username) == 0 {
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: parts[0],
|
||||
Format: parts[1],
|
||||
Version: version,
|
||||
Username: parts[0],
|
||||
Password: common.RandStr(16),
|
||||
Permission: "%",
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("sync from remote server failed, err: create user failed %v", err)
|
||||
}
|
||||
dataItem.Username = parts[0]
|
||||
dataItem.Permission = "%"
|
||||
} else {
|
||||
if isLocal {
|
||||
dataItem.Permission = "localhost"
|
||||
}
|
||||
if len(dataItem.Permission) == 0 {
|
||||
dataItem.Permission = strings.Join(permissionItem, ",")
|
||||
}
|
||||
}
|
||||
datas = append(datas, dataItem)
|
||||
}
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (r *Local) Close() {}
|
||||
|
||||
func (r *Local) ExecSQL(command string, timeout uint) error {
|
||||
itemCommand := r.PrefixCommand[:]
|
||||
itemCommand = append(itemCommand, command)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, "docker", itemCommand...)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (r *Local) ExecSQLForRows(command string, timeout uint) ([]string, error) {
|
||||
itemCommand := r.PrefixCommand[:]
|
||||
itemCommand = append(itemCommand, command)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, "docker", itemCommand...)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
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 nil, errors.New(stdStr)
|
||||
}
|
||||
return strings.Split(stdStr, "\n"), nil
|
||||
}
|
382
backend/utils/mysql/client/remote.go
Normal file
382
backend/utils/mysql/client/remote.go
Normal file
@@ -0,0 +1,382 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/mysql/helper"
|
||||
)
|
||||
|
||||
type Remote struct {
|
||||
Client *sql.DB
|
||||
From string
|
||||
User string
|
||||
Password string
|
||||
Address string
|
||||
Port uint
|
||||
}
|
||||
|
||||
func NewRemote(db Remote) *Remote {
|
||||
return &db
|
||||
}
|
||||
|
||||
func (r *Remote) Create(info CreateInfo) error {
|
||||
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", info.Name, info.Format, formatMap[info.Format])
|
||||
if err := r.ExecSQL(createSql, info.Timeout); err != nil {
|
||||
if strings.Contains(err.Error(), "ERROR 1007") {
|
||||
return buserr.New(constant.ErrDatabaseIsExist)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.CreateUser(info); err != nil {
|
||||
_ = r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) CreateUser(info CreateInfo) error {
|
||||
var userlist []string
|
||||
if strings.Contains(info.Permission, ",") {
|
||||
ips := strings.Split(info.Permission, ",")
|
||||
for _, ip := range ips {
|
||||
if len(ip) != 0 {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
|
||||
}
|
||||
|
||||
for _, user := range userlist {
|
||||
if err := r.ExecSQL(fmt.Sprintf("create user %s identified by '%s';", user, info.Password), info.Timeout); err != nil {
|
||||
_ = r.Delete(DeleteInfo{
|
||||
Name: info.Name,
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.Permission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300})
|
||||
if strings.Contains(err.Error(), "ERROR 1396") {
|
||||
return buserr.New(constant.ErrUserIsExist)
|
||||
}
|
||||
return err
|
||||
}
|
||||
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", info.Name, user)
|
||||
if info.Name == "*" {
|
||||
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
|
||||
}
|
||||
if strings.HasPrefix(info.Version, "5.7") || strings.HasPrefix(info.Version, "5.6") {
|
||||
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, info.Password)
|
||||
}
|
||||
if err := r.ExecSQL(grantStr, info.Timeout); err != nil {
|
||||
_ = r.Delete(DeleteInfo{
|
||||
Name: info.Name,
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.Permission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300})
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) Delete(info DeleteInfo) error {
|
||||
var userlist []string
|
||||
if strings.Contains(info.Permission, ",") {
|
||||
ips := strings.Split(info.Permission, ",")
|
||||
for _, ip := range ips {
|
||||
if len(ip) != 0 {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
|
||||
}
|
||||
|
||||
for _, user := range userlist {
|
||||
if strings.HasPrefix(info.Version, "5.6") {
|
||||
if err := r.ExecSQL(fmt.Sprintf("drop user %s", user), info.Timeout); err != nil && !info.ForceDelete {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := r.ExecSQL(fmt.Sprintf("drop user if exists %s", user), info.Timeout); err != nil && !info.ForceDelete {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(info.Name) != 0 {
|
||||
if err := r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout); err != nil && !info.ForceDelete {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !info.ForceDelete {
|
||||
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) ChangePassword(info PasswordChangeInfo) error {
|
||||
if info.Username != "root" {
|
||||
var userlist []string
|
||||
if strings.Contains(info.Permission, ",") {
|
||||
ips := strings.Split(info.Permission, ",")
|
||||
for _, ip := range ips {
|
||||
if len(ip) != 0 {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission))
|
||||
}
|
||||
|
||||
for _, user := range userlist {
|
||||
passwordChangeSql := fmt.Sprintf("set password for %s = password('%s')", user, info.Password)
|
||||
if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") {
|
||||
passwordChangeSql = fmt.Sprintf("ALTER USER %s IDENTIFIED WITH mysql_native_password BY '%s';", user, info.Password)
|
||||
}
|
||||
if err := r.ExecSQL(passwordChangeSql, info.Timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
hosts, err := r.ExecSQLForHosts(info.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, host := range hosts {
|
||||
if host == "%" || host == "localhost" {
|
||||
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Password)
|
||||
if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") {
|
||||
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Password)
|
||||
}
|
||||
if err := r.ExecSQL(passwordRootChangeCMD, info.Timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
|
||||
if info.Username == "root" {
|
||||
info.OldPermission = "%"
|
||||
info.Name = "*"
|
||||
info.Password = r.Password
|
||||
}
|
||||
if info.Permission != info.OldPermission {
|
||||
if err := r.Delete(DeleteInfo{
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Permission: info.OldPermission,
|
||||
ForceDelete: true,
|
||||
Timeout: 300}); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Username == "root" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: info.Name,
|
||||
Version: info.Version,
|
||||
Username: info.Username,
|
||||
Password: info.Password,
|
||||
Permission: info.Permission,
|
||||
Timeout: info.Timeout,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.ExecSQL("flush privileges", 300); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) Backup(info BackupInfo) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(info.TargetDir) {
|
||||
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
|
||||
}
|
||||
}
|
||||
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
|
||||
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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()
|
||||
}()
|
||||
}
|
||||
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F")
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := helper.Source(dns, f, helper.WithMergeInsert(1000)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) SyncDB(version string) ([]SyncDBInfo, error) {
|
||||
var datas []SyncDBInfo
|
||||
rows, err := r.Client.Query("SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA")
|
||||
if err != nil {
|
||||
return datas, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var dbName, charsetName string
|
||||
if err = rows.Scan(&dbName, &charsetName); err != nil {
|
||||
return datas, err
|
||||
}
|
||||
if dbName == "information_schema" || dbName == "mysql" || dbName == "performance_schema" || dbName == "sys" {
|
||||
continue
|
||||
}
|
||||
dataItem := SyncDBInfo{
|
||||
Name: dbName,
|
||||
From: r.From,
|
||||
MysqlName: r.From,
|
||||
Format: charsetName,
|
||||
}
|
||||
userRows, err := r.Client.Query("SELECT USER,HOST FROM mysql.DB WHERE DB = ?", dbName)
|
||||
if err != nil {
|
||||
return datas, err
|
||||
}
|
||||
|
||||
var permissionItem []string
|
||||
isLocal := true
|
||||
i := 0
|
||||
for userRows.Next() {
|
||||
var user, host string
|
||||
if err = userRows.Scan(&user, &host); err != nil {
|
||||
return datas, err
|
||||
}
|
||||
if user == "root" {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
dataItem.Username = user
|
||||
}
|
||||
if dataItem.Username == user && host == "%" {
|
||||
isLocal = false
|
||||
dataItem.Permission = "%"
|
||||
} else if dataItem.Username == user && host != "localhost" {
|
||||
isLocal = false
|
||||
permissionItem = append(permissionItem, host)
|
||||
}
|
||||
i++
|
||||
}
|
||||
if len(dataItem.Username) == 0 {
|
||||
if err := r.CreateUser(CreateInfo{
|
||||
Name: dbName,
|
||||
Format: charsetName,
|
||||
Version: version,
|
||||
Username: dbName,
|
||||
Password: common.RandStr(16),
|
||||
Permission: "%",
|
||||
Timeout: 300,
|
||||
}); err != nil {
|
||||
global.LOG.Errorf("sync from remote server failed, err: create user failed %v", err)
|
||||
}
|
||||
dataItem.Username = dbName
|
||||
dataItem.Permission = "%"
|
||||
} else {
|
||||
if isLocal {
|
||||
dataItem.Permission = "localhost"
|
||||
}
|
||||
if len(dataItem.Permission) == 0 {
|
||||
dataItem.Permission = strings.Join(permissionItem, ",")
|
||||
}
|
||||
}
|
||||
datas = append(datas, dataItem)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return datas, err
|
||||
}
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (r *Remote) Close() {
|
||||
_ = r.Client.Close()
|
||||
}
|
||||
|
||||
func (r *Remote) ExecSQL(command string, timeout uint) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := r.Client.ExecContext(ctx, command); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) ExecSQLForHosts(timeout uint) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
results, err := r.Client.QueryContext(ctx, "select host from mysql.user where user='root';")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
var rows []string
|
||||
for results.Next() {
|
||||
var host string
|
||||
if err := results.Scan(&host); err != nil {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, host)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
314
backend/utils/mysql/helper/dump.go
Normal file
314
backend/utils/mysql/helper/dump.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func init() {}
|
||||
|
||||
type dumpOption struct {
|
||||
isData bool
|
||||
|
||||
tables []string
|
||||
isAllTable bool
|
||||
isDropTable bool
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
type DumpOption func(*dumpOption)
|
||||
|
||||
func WithDropTable() DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.isDropTable = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithData() DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.isData = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithTables(tables ...string) DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.tables = tables
|
||||
}
|
||||
}
|
||||
|
||||
func WithAllTable() DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.isAllTable = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithWriter(writer io.Writer) DumpOption {
|
||||
return func(option *dumpOption) {
|
||||
option.writer = writer
|
||||
}
|
||||
}
|
||||
|
||||
func Dump(dns string, opts ...DumpOption) error {
|
||||
start := time.Now()
|
||||
global.LOG.Infof("dump start at %s\n", start.Format("2006-01-02 15:04:05"))
|
||||
defer func() {
|
||||
end := time.Now()
|
||||
global.LOG.Infof("dump end at %s, cost %s\n", end.Format("2006-01-02 15:04:05"), end.Sub(start))
|
||||
}()
|
||||
|
||||
var err error
|
||||
|
||||
var o dumpOption
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
if len(o.tables) == 0 {
|
||||
o.isAllTable = true
|
||||
}
|
||||
|
||||
if o.writer == nil {
|
||||
o.writer = os.Stdout
|
||||
}
|
||||
|
||||
buf := bufio.NewWriter(o.writer)
|
||||
defer buf.Flush()
|
||||
|
||||
itemFile, lineNumber := "", 0
|
||||
|
||||
itemFile += "-- ----------------------------\n"
|
||||
itemFile += "-- MySQL Database Dump\n"
|
||||
itemFile += "-- Start Time: " + start.Format("2006-01-02 15:04:05") + "\n"
|
||||
itemFile += "-- ----------------------------\n\n\n"
|
||||
|
||||
db, err := sql.Open("mysql", dns)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("open mysql db failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
dbName, err := getDBNameFromDNS(dns)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get db name from dns failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
_, err = db.Exec(fmt.Sprintf("USE `%s`", dbName))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("exec `use %s` failed, err: %v", dbName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
var tables []string
|
||||
if o.isAllTable {
|
||||
tmp, err := getAllTables(db)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get all tables failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
tables = tmp
|
||||
} else {
|
||||
tables = o.tables
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
if o.isDropTable {
|
||||
itemFile += fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table)
|
||||
}
|
||||
|
||||
itemFile += "-- ----------------------------\n"
|
||||
itemFile += fmt.Sprintf("-- Table structure for %s\n", table)
|
||||
itemFile += "-- ----------------------------\n"
|
||||
|
||||
createTableSQL, err := getCreateTableSQL(db, table)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get create table sql failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
itemFile += createTableSQL
|
||||
itemFile += ";\n\n\n\n"
|
||||
|
||||
if o.isData {
|
||||
itemFile += "-- ----------------------------\n"
|
||||
itemFile += fmt.Sprintf("-- Records of %s\n", table)
|
||||
itemFile += "-- ----------------------------\n"
|
||||
|
||||
lineRows, err := db.Query(fmt.Sprintf("SELECT * FROM `%s`", table))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("exec `select * from %s` failed, err: %v", table, err)
|
||||
return err
|
||||
}
|
||||
defer lineRows.Close()
|
||||
|
||||
var columns []string
|
||||
columns, err = lineRows.Columns()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get columes falied, err: %v", err)
|
||||
return err
|
||||
}
|
||||
columnTypes, err := lineRows.ColumnTypes()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get colume types failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
for lineRows.Next() {
|
||||
row := make([]interface{}, len(columns))
|
||||
rowPointers := make([]interface{}, len(columns))
|
||||
for i := range columns {
|
||||
rowPointers[i] = &row[i]
|
||||
}
|
||||
if err = lineRows.Scan(rowPointers...); err != nil {
|
||||
global.LOG.Errorf("scan row data failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
ssql := loadDataSql(row, columnTypes, table)
|
||||
if len(ssql) != 0 {
|
||||
itemFile += ssql
|
||||
lineNumber++
|
||||
}
|
||||
if lineNumber > 500 {
|
||||
_, _ = buf.WriteString(itemFile)
|
||||
itemFile = ""
|
||||
lineNumber = 0
|
||||
}
|
||||
}
|
||||
|
||||
itemFile += "\n\n"
|
||||
}
|
||||
}
|
||||
|
||||
itemFile += "-- ----------------------------\n"
|
||||
itemFile += "-- Dumped by mysqldump\n"
|
||||
itemFile += "-- Cost Time: " + time.Since(start).String() + "\n"
|
||||
itemFile += "-- ----------------------------\n"
|
||||
|
||||
_, _ = buf.WriteString(itemFile)
|
||||
_ = buf.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCreateTableSQL(db *sql.DB, table string) (string, error) {
|
||||
var createTableSQL string
|
||||
err := db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE `%s`", table)).Scan(&table, &createTableSQL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
createTableSQL = strings.Replace(createTableSQL, "CREATE TABLE", "CREATE TABLE IF NOT EXISTS", 1)
|
||||
return createTableSQL, nil
|
||||
}
|
||||
|
||||
func getAllTables(db *sql.DB) ([]string, error) {
|
||||
var tables []string
|
||||
rows, err := db.Query("SHOW TABLES")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var table string
|
||||
err = rows.Scan(&table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tables = append(tables, table)
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func loadDataSql(row []interface{}, columnTypes []*sql.ColumnType, table string) string {
|
||||
ssql := "INSERT INTO `" + table + "` VALUES ("
|
||||
for i, col := range row {
|
||||
if col == nil {
|
||||
ssql += "NULL"
|
||||
} else {
|
||||
Type := columnTypes[i].DatabaseTypeName()
|
||||
Type = strings.Replace(Type, "UNSIGNED", "", -1)
|
||||
Type = strings.Replace(Type, " ", "", -1)
|
||||
switch Type {
|
||||
case "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "INTEGER", "BIGINT":
|
||||
if bs, ok := col.([]byte); ok {
|
||||
ssql += string(bs)
|
||||
} else {
|
||||
ssql += fmt.Sprintf("%d", col)
|
||||
}
|
||||
case "FLOAT", "DOUBLE":
|
||||
if bs, ok := col.([]byte); ok {
|
||||
ssql += string(bs)
|
||||
} else {
|
||||
ssql += fmt.Sprintf("%f", col)
|
||||
}
|
||||
case "DECIMAL", "DEC":
|
||||
ssql += fmt.Sprintf("%s", col)
|
||||
|
||||
case "DATE":
|
||||
t, ok := col.(time.Time)
|
||||
if !ok {
|
||||
global.LOG.Errorf("the DATE type conversion failed, err value: %v", col)
|
||||
return ""
|
||||
}
|
||||
ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02"))
|
||||
case "DATETIME":
|
||||
t, ok := col.(time.Time)
|
||||
if !ok {
|
||||
global.LOG.Errorf("the DATETIME type conversion failed, err value: %v", col)
|
||||
return ""
|
||||
}
|
||||
ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02 15:04:05"))
|
||||
case "TIMESTAMP":
|
||||
t, ok := col.(time.Time)
|
||||
if !ok {
|
||||
global.LOG.Errorf("the TIMESTAMP type conversion failed, err value: %v", col)
|
||||
return ""
|
||||
}
|
||||
ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02 15:04:05"))
|
||||
case "TIME":
|
||||
t, ok := col.([]byte)
|
||||
if !ok {
|
||||
global.LOG.Errorf("the TIME type conversion failed, err value: %v", col)
|
||||
return ""
|
||||
}
|
||||
ssql += fmt.Sprintf("'%s'", string(t))
|
||||
case "YEAR":
|
||||
t, ok := col.([]byte)
|
||||
if !ok {
|
||||
global.LOG.Errorf("the YEAR type conversion failed, err value: %v", col)
|
||||
return ""
|
||||
}
|
||||
ssql += string(t)
|
||||
case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT":
|
||||
ssql += fmt.Sprintf("'%s'", strings.Replace(fmt.Sprintf("%s", col), "'", "''", -1))
|
||||
case "BIT", "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB":
|
||||
ssql += fmt.Sprintf("0x%X", col)
|
||||
case "ENUM", "SET":
|
||||
ssql += fmt.Sprintf("'%s'", col)
|
||||
case "BOOL", "BOOLEAN":
|
||||
if col.(bool) {
|
||||
ssql += "true"
|
||||
} else {
|
||||
ssql += "false"
|
||||
}
|
||||
case "JSON":
|
||||
ssql += fmt.Sprintf("'%s'", col)
|
||||
default:
|
||||
global.LOG.Errorf("unsupported colume type: %s", Type)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
if i < len(row)-1 {
|
||||
ssql += ","
|
||||
}
|
||||
}
|
||||
ssql += ");\n"
|
||||
return ssql
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user