2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 16:10:59 +08:00

feat: Rsync 插件

This commit is contained in:
耗子
2023-11-23 21:38:48 +08:00
parent 596cd68728
commit a368117e00
10 changed files with 253 additions and 216 deletions

View File

@@ -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("参数错误")

View File

@@ -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())

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -1295,7 +1295,7 @@ const docTemplate = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/panel_app_http_requests_website.Add"
"$ref": "#/definitions/requests.Add"
}
}
],
@@ -1696,40 +1696,6 @@ const docTemplate = `{
}
}
},
"put": {
"security": [
{
"BearerToken": []
}
],
"description": "更新 Rsync 模块",
"produces": [
"application/json"
],
"tags": [
"插件-Rsync"
],
"summary": "更新模块",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Update"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
},
"post": {
"security": [
{
@@ -1751,7 +1717,50 @@ const docTemplate = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Add"
"$ref": "#/definitions/requests.Create"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
}
},
"/plugins/rsync/modules/{name}": {
"post": {
"security": [
{
"BearerToken": []
}
],
"description": "更新 Rsync 模块",
"produces": [
"application/json"
],
"tags": [
"插件-Rsync"
],
"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.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": {

View File

@@ -1288,7 +1288,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/panel_app_http_requests_website.Add"
"$ref": "#/definitions/requests.Add"
}
}
],
@@ -1689,40 +1689,6 @@
}
}
},
"put": {
"security": [
{
"BearerToken": []
}
],
"description": "更新 Rsync 模块",
"produces": [
"application/json"
],
"tags": [
"插件-Rsync"
],
"summary": "更新模块",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Update"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
},
"post": {
"security": [
{
@@ -1744,7 +1710,50 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/panel_app_http_requests_plugins_rsync.Add"
"$ref": "#/definitions/requests.Create"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
}
},
"/plugins/rsync/modules/{name}": {
"post": {
"security": [
{
"BearerToken": []
}
],
"description": "更新 Rsync 模块",
"produces": [
"application/json"
],
"tags": [
"插件-Rsync"
],
"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.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": {

View File

@@ -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,26 +1474,6 @@ paths:
tags:
- 网站管理
/plugins/rsync/modules:
delete:
description: 删除 Rsync 模块
parameters:
- description: 模块名称
in: query
name: name
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.SuccessResponse'
security:
- BearerToken: []
summary: 删除模块
tags:
- 插件-Rsync
get:
description: 列出所有 Rsync 模块
parameters:
@@ -1523,7 +1503,7 @@ paths:
name: data
required: true
schema:
$ref: '#/definitions/panel_app_http_requests_plugins_rsync.Add'
$ref: '#/definitions/requests.Create'
produces:
- application/json
responses:
@@ -1536,9 +1516,35 @@ paths:
summary: 添加模块
tags:
- 插件-Rsync
put:
/plugins/rsync/modules/{name}:
delete:
description: 删除 Rsync 模块
parameters:
- description: 模块名称
in: path
name: name
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.SuccessResponse'
security:
- BearerToken: []
summary: 删除模块
tags:
- 插件-Rsync
post:
description: 更新 Rsync 模块
parameters:
- description: 模块名称
in: path
name: name
required: true
type: string
- description: request
in: body
name: data

View File

@@ -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()

View File

@@ -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

View File

@@ -36,3 +36,5 @@ if [ "$?" != "0" ]; then
echo "错误fail2ban安装失败请截图错误信息寻求帮助。"
exit 1
fi
systemctl restart fail2ban