From a368117e00cd6b63c3fd40d0ffcb12ed2269f6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Thu, 23 Nov 2023 21:38:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Rsync=20=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/console/commands/panel.go | 4 +- .../controllers/plugins/rsync_controller.go | 79 +++++++++++------- app/http/requests/common/paginate.go | 17 ++-- .../plugins/rsync/{add.go => create.go} | 12 +-- docs/docs.go | 77 ++++++++++-------- docs/swagger.json | 77 ++++++++++-------- docs/swagger.yaml | 80 ++++++++++--------- routes/plugin.go | 5 +- scripts/fail2ban/install.sh | 4 +- scripts/fail2ban/update.sh | 2 + 10 files changed, 197 insertions(+), 160 deletions(-) rename app/http/requests/plugins/rsync/{add.go => create.go} (70%) diff --git a/app/console/commands/panel.go b/app/console/commands/panel.go index e2a3868fab..2505ad5fc4 100644 --- a/app/console/commands/panel.go +++ b/app/console/commands/panel.go @@ -450,8 +450,8 @@ func (receiver *Panel) Handle(ctx console.Context) error { case "writeSite": name := arg1 status := cast.ToBool(arg2) - path := ctx.Argument(3) - php := cast.ToInt(ctx.Argument(4)) + path := arg3 + php := cast.ToInt(arg4) ssl := cast.ToBool(ctx.Argument(5)) if len(name) == 0 || len(path) == 0 { color.Redln("参数错误") diff --git a/app/http/controllers/plugins/rsync_controller.go b/app/http/controllers/plugins/rsync_controller.go index 4b8c529231..f2d7375a3f 100644 --- a/app/http/controllers/plugins/rsync_controller.go +++ b/app/http/controllers/plugins/rsync_controller.go @@ -1,6 +1,7 @@ package plugins import ( + "regexp" "strings" "github.com/goravel/framework/contracts/http" @@ -133,7 +134,7 @@ func (r *RsyncController) List(ctx http.Context) http.Response { modules = append(modules, *currentModule) } moduleName := line[1 : len(line)-1] - secret, err := tools.Exec("grep -E '^" + moduleName + ":.*$' /etc/rsyncd.secrets | awk '{print $2}'") + secret, err := tools.Exec("grep -E '^" + moduleName + ":.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'") if err != nil { return controllers.Error(ctx, http.StatusInternalServerError, "获取模块"+moduleName+"的密钥失败") } @@ -189,40 +190,47 @@ func (r *RsyncController) List(ctx http.Context) http.Response { }) } -// Add +// Create // // @Summary 添加模块 // @Description 添加 Rsync 模块 // @Tags 插件-Rsync // @Produce json // @Security BearerToken -// @Param data body requests.Add true "request" +// @Param data body requests.Create true "request" // @Success 200 {object} controllers.SuccessResponse // @Router /plugins/rsync/modules [post] -func (r *RsyncController) Add(ctx http.Context) http.Response { - var addRequest requests.Add - sanitize := controllers.Sanitize(ctx, &addRequest) +func (r *RsyncController) Create(ctx http.Context) http.Response { + var createRequest requests.Create + sanitize := controllers.Sanitize(ctx, &createRequest) if sanitize != nil { return sanitize } - conf := ` -# ` + addRequest.Name + `-START -[` + addRequest.Name + `] -path = ` + addRequest.Path + ` -comment = ` + addRequest.Comment + ` + config, err := tools.Read("/etc/rsyncd.conf") + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) + } + if strings.Contains(config, "["+createRequest.Name+"]") { + return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+createRequest.Name+" 已存在") + } + + conf := `# ` + createRequest.Name + `-START +[` + createRequest.Name + `] +path = ` + createRequest.Path + ` +comment = ` + createRequest.Comment + ` read only = no -auth users = ` + addRequest.AuthUser + ` -hosts allow = ` + addRequest.HostsAllow + ` +auth users = ` + createRequest.AuthUser + ` +hosts allow = ` + createRequest.HostsAllow + ` secrets file = /etc/rsyncd.secrets -# ` + addRequest.Name + `-END +# ` + createRequest.Name + `-END ` if err := tools.WriteAppend("/etc/rsyncd.conf", conf); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if err := tools.WriteAppend("/etc/rsyncd.secrets", addRequest.Name+":"+addRequest.Secret); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) + if out, err := tools.Exec("echo '" + createRequest.AuthUser + ":" + createRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, out) } if err := tools.ServiceRestart("rsyncd"); err != nil { @@ -232,17 +240,17 @@ secrets file = /etc/rsyncd.secrets return controllers.Success(ctx, nil) } -// Delete +// Destroy // // @Summary 删除模块 // @Description 删除 Rsync 模块 // @Tags 插件-Rsync // @Produce json // @Security BearerToken -// @Param name query string true "模块名称" +// @Param name path string true "模块名称" // @Success 200 {object} controllers.SuccessResponse -// @Router /plugins/rsync/modules [delete] -func (r *RsyncController) Delete(ctx http.Context) http.Response { +// @Router /plugins/rsync/modules/{name} [delete] +func (r *RsyncController) Destroy(ctx http.Context) http.Response { name := ctx.Request().Input("name") if len(name) == 0 { return controllers.Error(ctx, http.StatusUnprocessableEntity, "name 不能为空") @@ -259,12 +267,17 @@ func (r *RsyncController) Delete(ctx http.Context) http.Response { module := tools.Cut(config, "# "+name+"-START", "# "+name+"-END") config = strings.Replace(config, "\n# "+name+"-START"+module+"# "+name+"-END", "", -1) + match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module) + if len(match) == 2 { + authUser := match[1] + if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, out) + } + } + if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := tools.Exec("sed -i '/^" + name + ":.*$/d' /etc/rsyncd.secrets"); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } if err = tools.ServiceRestart("rsyncd"); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) @@ -280,9 +293,10 @@ func (r *RsyncController) Delete(ctx http.Context) http.Response { // @Tags 插件-Rsync // @Produce json // @Security BearerToken +// @Param name path string true "模块名称" // @Param data body requests.Update true "request" // @Success 200 {object} controllers.SuccessResponse -// @Router /plugins/rsync/modules [put] +// @Router /plugins/rsync/modules/{name} [post] func (r *RsyncController) Update(ctx http.Context) http.Response { var updateRequest requests.Update sanitize := controllers.Sanitize(ctx, &updateRequest) @@ -294,7 +308,6 @@ func (r *RsyncController) Update(ctx http.Context) http.Response { if err != nil { return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if !strings.Contains(config, "["+updateRequest.Name+"]") { return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+updateRequest.Name+" 不存在") } @@ -307,21 +320,25 @@ read only = no auth users = ` + updateRequest.AuthUser + ` hosts allow = ` + updateRequest.HostsAllow + ` secrets file = /etc/rsyncd.secrets -# ` + updateRequest.Name + `-END -` +# ` + updateRequest.Name + `-END` module := tools.Cut(config, "# "+updateRequest.Name+"-START", "# "+updateRequest.Name+"-END") config = strings.Replace(config, "# "+updateRequest.Name+"-START"+module+"# "+updateRequest.Name+"-END", newConf, -1) + match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module) + if len(match) == 2 { + authUser := match[1] + if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, out) + } + } + if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := tools.Exec("sed -i '/^" + updateRequest.Name + ":.*$/d' /etc/rsyncd.secrets"); err != nil { + if out, err := tools.Exec("echo '" + updateRequest.AuthUser + ":" + updateRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, out) } - if err := tools.WriteAppend("/etc/rsyncd.secrets", updateRequest.Name+":"+updateRequest.Secret); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) - } if err = tools.ServiceRestart("rsyncd"); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) diff --git a/app/http/requests/common/paginate.go b/app/http/requests/common/paginate.go index 72061a45b1..fd1c9b7435 100644 --- a/app/http/requests/common/paginate.go +++ b/app/http/requests/common/paginate.go @@ -6,8 +6,8 @@ import ( ) type Paginate struct { - Page int `form:"page" json:"page"` - Limit int `form:"limit" json:"limit"` + Page int `form:"page" json:"page" filter:"int"` + Limit int `form:"limit" json:"limit" filter:"int"` } func (r *Paginate) Authorize(ctx http.Context) error { @@ -16,20 +16,13 @@ func (r *Paginate) Authorize(ctx http.Context) error { func (r *Paginate) Rules(ctx http.Context) map[string]string { return map[string]string{ - "page": "required|uint|min:1", - "limit": "required|uint|min:1", + "page": "required|int|min:1", + "limit": "required|int|min:1", } } func (r *Paginate) Messages(ctx http.Context) map[string]string { - return map[string]string{ - "page.required": "分页参数 page 不能为空", - "page.uint": "分页参数 page 必须是一个整数", - "page.min": "分页参数 page 必须大于等于 1", - "limit.required": "分页参数 limit 不能为空", - "limit.uint": "分页参数 limit 必须是一个整数", - "limit.min": "分页参数 limit 必须大于等于 1", - } + return map[string]string{} } func (r *Paginate) Attributes(ctx http.Context) map[string]string { diff --git a/app/http/requests/plugins/rsync/add.go b/app/http/requests/plugins/rsync/create.go similarity index 70% rename from app/http/requests/plugins/rsync/add.go rename to app/http/requests/plugins/rsync/create.go index 70f3a8d3a8..0ca73d0a37 100644 --- a/app/http/requests/plugins/rsync/add.go +++ b/app/http/requests/plugins/rsync/create.go @@ -5,7 +5,7 @@ import ( "github.com/goravel/framework/contracts/validation" ) -type Add struct { +type Create struct { Name string `form:"name" json:"name"` Path string `form:"path" json:"path"` Comment string `form:"comment" json:"comment"` @@ -14,11 +14,11 @@ type Add struct { HostsAllow string `form:"hosts_allow" json:"hosts_allow"` } -func (r *Add) Authorize(ctx http.Context) error { +func (r *Create) Authorize(ctx http.Context) error { return nil } -func (r *Add) Rules(ctx http.Context) map[string]string { +func (r *Create) Rules(ctx http.Context) map[string]string { return map[string]string{ "name": "required|regex:^[a-zA-Z0-9-_]+$", "path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$", @@ -29,14 +29,14 @@ func (r *Add) Rules(ctx http.Context) map[string]string { } } -func (r *Add) Messages(ctx http.Context) map[string]string { +func (r *Create) Messages(ctx http.Context) map[string]string { return map[string]string{} } -func (r *Add) Attributes(ctx http.Context) map[string]string { +func (r *Create) Attributes(ctx http.Context) map[string]string { return map[string]string{} } -func (r *Add) PrepareForValidation(ctx http.Context, data validation.Data) error { +func (r *Create) PrepareForValidation(ctx http.Context, data validation.Data) error { return nil } diff --git a/docs/docs.go b/docs/docs.go index d88baeb78f..bbe154035d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1295,7 +1295,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/panel_app_http_requests_website.Add" + "$ref": "#/definitions/requests.Add" } } ], @@ -1696,20 +1696,20 @@ const docTemplate = `{ } } }, - "put": { + "post": { "security": [ { "BearerToken": [] } ], - "description": "更新 Rsync 模块", + "description": "添加 Rsync 模块", "produces": [ "application/json" ], "tags": [ "插件-Rsync" ], - "summary": "更新模块", + "summary": "添加模块", "parameters": [ { "description": "request", @@ -1717,7 +1717,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Update" + "$ref": "#/definitions/requests.Create" } } ], @@ -1729,29 +1729,38 @@ const docTemplate = `{ } } } - }, + } + }, + "/plugins/rsync/modules/{name}": { "post": { "security": [ { "BearerToken": [] } ], - "description": "添加 Rsync 模块", + "description": "更新 Rsync 模块", "produces": [ "application/json" ], "tags": [ "插件-Rsync" ], - "summary": "添加模块", + "summary": "更新模块", "parameters": [ + { + "type": "string", + "description": "模块名称", + "name": "name", + "in": "path", + "required": true + }, { "description": "request", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Add" + "$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Update" } } ], @@ -1783,7 +1792,7 @@ const docTemplate = `{ "type": "string", "description": "模块名称", "name": "name", - "in": "query", + "in": "path", "required": true } ], @@ -2141,29 +2150,6 @@ const docTemplate = `{ } } }, - "panel_app_http_requests_plugins_rsync.Add": { - "type": "object", - "properties": { - "auth_user": { - "type": "string" - }, - "comment": { - "type": "string" - }, - "hosts_allow": { - "type": "string" - }, - "name": { - "type": "string" - }, - "path": { - "type": "string" - }, - "secret": { - "type": "string" - } - } - }, "panel_app_http_requests_plugins_rsync.Update": { "type": "object", "properties": { @@ -2216,7 +2202,7 @@ const docTemplate = `{ } } }, - "panel_app_http_requests_website.Add": { + "requests.Add": { "type": "object", "properties": { "db": { @@ -2312,6 +2298,29 @@ const docTemplate = `{ } } }, + "requests.Create": { + "type": "object", + "properties": { + "auth_user": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "hosts_allow": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, "requests.DNSStore": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 0339956b8a..938c8dd93d 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1288,7 +1288,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/panel_app_http_requests_website.Add" + "$ref": "#/definitions/requests.Add" } } ], @@ -1689,20 +1689,20 @@ } } }, - "put": { + "post": { "security": [ { "BearerToken": [] } ], - "description": "更新 Rsync 模块", + "description": "添加 Rsync 模块", "produces": [ "application/json" ], "tags": [ "插件-Rsync" ], - "summary": "更新模块", + "summary": "添加模块", "parameters": [ { "description": "request", @@ -1710,7 +1710,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Update" + "$ref": "#/definitions/requests.Create" } } ], @@ -1722,29 +1722,38 @@ } } } - }, + } + }, + "/plugins/rsync/modules/{name}": { "post": { "security": [ { "BearerToken": [] } ], - "description": "添加 Rsync 模块", + "description": "更新 Rsync 模块", "produces": [ "application/json" ], "tags": [ "插件-Rsync" ], - "summary": "添加模块", + "summary": "更新模块", "parameters": [ + { + "type": "string", + "description": "模块名称", + "name": "name", + "in": "path", + "required": true + }, { "description": "request", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Add" + "$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Update" } } ], @@ -1776,7 +1785,7 @@ "type": "string", "description": "模块名称", "name": "name", - "in": "query", + "in": "path", "required": true } ], @@ -2134,29 +2143,6 @@ } } }, - "panel_app_http_requests_plugins_rsync.Add": { - "type": "object", - "properties": { - "auth_user": { - "type": "string" - }, - "comment": { - "type": "string" - }, - "hosts_allow": { - "type": "string" - }, - "name": { - "type": "string" - }, - "path": { - "type": "string" - }, - "secret": { - "type": "string" - } - } - }, "panel_app_http_requests_plugins_rsync.Update": { "type": "object", "properties": { @@ -2209,7 +2195,7 @@ } } }, - "panel_app_http_requests_website.Add": { + "requests.Add": { "type": "object", "properties": { "db": { @@ -2305,6 +2291,29 @@ } } }, + "requests.Create": { + "type": "object", + "properties": { + "auth_user": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "hosts_allow": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, "requests.DNSStore": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 35ed250f39..9358c60beb 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -151,21 +151,6 @@ definitions: updated_at: type: string type: object - panel_app_http_requests_plugins_rsync.Add: - properties: - auth_user: - type: string - comment: - type: string - hosts_allow: - type: string - name: - type: string - path: - type: string - secret: - type: string - type: object panel_app_http_requests_plugins_rsync.Update: properties: auth_user: @@ -200,7 +185,7 @@ definitions: website_path: type: string type: object - panel_app_http_requests_website.Add: + requests.Add: properties: db: type: boolean @@ -263,6 +248,21 @@ definitions: website_id: type: integer type: object + requests.Create: + properties: + auth_user: + type: string + comment: + type: string + hosts_allow: + type: string + name: + type: string + path: + type: string + secret: + type: string + type: object requests.DNSStore: properties: data: @@ -1243,7 +1243,7 @@ paths: name: data required: true schema: - $ref: '#/definitions/panel_app_http_requests_website.Add' + $ref: '#/definitions/requests.Add' produces: - application/json responses: @@ -1474,14 +1474,15 @@ paths: tags: - 网站管理 /plugins/rsync/modules: - delete: - description: 删除 Rsync 模块 + get: + description: 列出所有 Rsync 模块 parameters: - - description: 模块名称 - in: query - name: name + - description: request + in: body + name: data required: true - type: string + schema: + $ref: '#/definitions/commonrequests.Paginate' produces: - application/json responses: @@ -1491,18 +1492,18 @@ paths: $ref: '#/definitions/controllers.SuccessResponse' security: - BearerToken: [] - summary: 删除模块 + summary: 列出模块 tags: - 插件-Rsync - get: - description: 列出所有 Rsync 模块 + post: + description: 添加 Rsync 模块 parameters: - description: request in: body name: data required: true schema: - $ref: '#/definitions/commonrequests.Paginate' + $ref: '#/definitions/requests.Create' produces: - application/json responses: @@ -1512,18 +1513,18 @@ paths: $ref: '#/definitions/controllers.SuccessResponse' security: - BearerToken: [] - summary: 列出模块 + summary: 添加模块 tags: - 插件-Rsync - post: - description: 添加 Rsync 模块 + /plugins/rsync/modules/{name}: + delete: + description: 删除 Rsync 模块 parameters: - - description: request - in: body - name: data + - description: 模块名称 + in: path + name: name required: true - schema: - $ref: '#/definitions/panel_app_http_requests_plugins_rsync.Add' + type: string produces: - application/json responses: @@ -1533,12 +1534,17 @@ paths: $ref: '#/definitions/controllers.SuccessResponse' security: - BearerToken: [] - summary: 添加模块 + summary: 删除模块 tags: - 插件-Rsync - put: + post: description: 更新 Rsync 模块 parameters: + - description: 模块名称 + in: path + name: name + required: true + type: string - description: request in: body name: data diff --git a/routes/plugin.go b/routes/plugin.go index 2c88b543d2..e0d0944aae 100644 --- a/routes/plugin.go +++ b/routes/plugin.go @@ -289,8 +289,9 @@ func Plugin() { route.Post("stop", rsyncController.Stop) route.Post("restart", rsyncController.Restart) route.Get("modules", rsyncController.List) - route.Post("modules", rsyncController.Add) - route.Delete("modules", rsyncController.Delete) + route.Post("modules", rsyncController.Create) + route.Post("modules/{name}", rsyncController.Update) + route.Delete("modules/{name}", rsyncController.Destroy) }) r.Prefix("toolbox").Group(func(route route.Router) { toolboxController := plugins.NewToolBoxController() diff --git a/scripts/fail2ban/install.sh b/scripts/fail2ban/install.sh index 414dcbf556..69cfad1a3f 100644 --- a/scripts/fail2ban/install.sh +++ b/scripts/fail2ban/install.sh @@ -75,9 +75,9 @@ if [ "${OS}" == "debian" ]; then fi # 启动 fail2ban -systemctl unmask fail2ban systemctl daemon-reload +systemctl unmask fail2ban systemctl enable fail2ban -systemctl restart fail2ban +systemctl start fail2ban panel writePlugin fail2ban 1.0.0 diff --git a/scripts/fail2ban/update.sh b/scripts/fail2ban/update.sh index 491ff9c702..e72c1a528b 100644 --- a/scripts/fail2ban/update.sh +++ b/scripts/fail2ban/update.sh @@ -36,3 +36,5 @@ if [ "$?" != "0" ]; then echo "错误:fail2ban安装失败,请截图错误信息寻求帮助。" exit 1 fi + +systemctl restart fail2ban