-
-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
1,539 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,331 @@ | ||
package plugins | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/goravel/framework/contracts/http" | ||
|
||
"panel/app/http/controllers" | ||
commonrequests "panel/app/http/requests/common" | ||
requests "panel/app/http/requests/plugins/rsync" | ||
"panel/app/services" | ||
"panel/pkg/tools" | ||
) | ||
|
||
type RsyncController struct { | ||
setting services.Setting | ||
} | ||
|
||
func NewRsyncController() *RsyncController { | ||
return &RsyncController{ | ||
setting: services.NewSettingImpl(), | ||
} | ||
} | ||
|
||
// Status | ||
// | ||
// @Summary 服务状态 | ||
// @Description 获取 Rsync 服务状态 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Success 200 {object} controllers.SuccessResponse | ||
// @Router /plugins/rsync/status [get] | ||
func (r *RsyncController) Status(ctx http.Context) http.Response { | ||
status, err := tools.ServiceStatus("rsyncd") | ||
if err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败") | ||
} | ||
|
||
return controllers.Success(ctx, status) | ||
} | ||
|
||
// Restart | ||
// | ||
// @Summary 重启服务 | ||
// @Description 重启 Rsync 服务 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Success 200 {object} controllers.SuccessResponse | ||
// @Router /plugins/rsync/restart [post] | ||
func (r *RsyncController) Restart(ctx http.Context) http.Response { | ||
if err := tools.ServiceRestart("rsyncd"); err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, "重启服务失败") | ||
} | ||
|
||
return controllers.Success(ctx, nil) | ||
} | ||
|
||
// Start | ||
// | ||
// @Summary 启动服务 | ||
// @Description 启动 Rsync 服务 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Success 200 {object} controllers.SuccessResponse | ||
// @Router /plugins/rsync/start [post] | ||
func (r *RsyncController) Start(ctx http.Context) http.Response { | ||
if err := tools.ServiceStart("rsyncd"); err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, "启动服务失败") | ||
} | ||
|
||
return controllers.Success(ctx, nil) | ||
} | ||
|
||
// Stop | ||
// | ||
// @Summary 停止服务 | ||
// @Description 停止 Rsync 服务 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Success 200 {object} controllers.SuccessResponse | ||
// @Router /plugins/rsync/stop [post] | ||
func (r *RsyncController) Stop(ctx http.Context) http.Response { | ||
if err := tools.ServiceStop("rsyncd"); err != nil { | ||
return nil | ||
} | ||
status, err := tools.ServiceStatus("rsyncd") | ||
if err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败") | ||
} | ||
|
||
return controllers.Success(ctx, !status) | ||
} | ||
|
||
// List | ||
// | ||
// @Summary 列出模块 | ||
// @Description 列出所有 Rsync 模块 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Param data body commonrequests.Paginate true "request" | ||
// @Success 200 {object} controllers.SuccessResponse | ||
// @Router /plugins/rsync/modules [get] | ||
func (r *RsyncController) List(ctx http.Context) http.Response { | ||
var paginateRequest commonrequests.Paginate | ||
sanitize := controllers.Sanitize(ctx, &paginateRequest) | ||
if sanitize != nil { | ||
return sanitize | ||
} | ||
|
||
config, err := tools.Read("/etc/rsyncd.conf") | ||
if err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) | ||
} | ||
|
||
var modules []RsyncModule | ||
lines := strings.Split(config, "\n") | ||
var currentModule *RsyncModule | ||
|
||
for _, line := range lines { | ||
line = strings.TrimSpace(line) | ||
|
||
if line == "" || strings.HasPrefix(line, "#") { | ||
continue | ||
} | ||
|
||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { | ||
if currentModule != nil { | ||
modules = append(modules, *currentModule) | ||
} | ||
moduleName := line[1 : len(line)-1] | ||
secret, err := tools.Exec("grep -E '^" + moduleName + ":.*$' /etc/rsyncd.secrets | awk '{print $2}'") | ||
if err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, "获取模块"+moduleName+"的密钥失败") | ||
} | ||
currentModule = &RsyncModule{ | ||
Name: moduleName, | ||
Secret: secret, | ||
} | ||
} else if currentModule != nil { | ||
parts := strings.SplitN(line, "=", 2) | ||
if len(parts) == 2 { | ||
key := strings.TrimSpace(parts[0]) | ||
value := strings.TrimSpace(parts[1]) | ||
|
||
switch key { | ||
case "path": | ||
currentModule.Path = value | ||
case "comment": | ||
currentModule.Comment = value | ||
case "read only": | ||
currentModule.ReadOnly = value == "yes" || value == "true" | ||
case "auth users": | ||
currentModule.AuthUser = value | ||
case "hosts allow": | ||
currentModule.HostsAllow = value | ||
} | ||
} | ||
} | ||
} | ||
|
||
if currentModule != nil { | ||
modules = append(modules, *currentModule) | ||
} | ||
|
||
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit | ||
endIndex := paginateRequest.Page * paginateRequest.Limit | ||
if startIndex > len(modules) { | ||
return controllers.Success(ctx, http.Json{ | ||
"total": 0, | ||
"items": []RsyncModule{}, | ||
}) | ||
} | ||
if endIndex > len(modules) { | ||
endIndex = len(modules) | ||
} | ||
pagedModules := modules[startIndex:endIndex] | ||
if pagedModules == nil { | ||
pagedModules = []RsyncModule{} | ||
} | ||
|
||
return controllers.Success(ctx, http.Json{ | ||
"total": len(modules), | ||
"items": pagedModules, | ||
}) | ||
} | ||
|
||
// Add | ||
// | ||
// @Summary 添加模块 | ||
// @Description 添加 Rsync 模块 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Param data body requests.Add 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) | ||
if sanitize != nil { | ||
return sanitize | ||
} | ||
|
||
conf := ` | ||
# ` + addRequest.Name + `-START | ||
[` + addRequest.Name + `] | ||
path = ` + addRequest.Path + ` | ||
comment = ` + addRequest.Comment + ` | ||
read only = no | ||
auth users = ` + addRequest.AuthUser + ` | ||
hosts allow = ` + addRequest.HostsAllow + ` | ||
secrets file = /etc/rsyncd.secrets | ||
# ` + addRequest.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 err := tools.ServiceRestart("rsyncd"); err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) | ||
} | ||
|
||
return controllers.Success(ctx, nil) | ||
} | ||
|
||
// Delete | ||
// | ||
// @Summary 删除模块 | ||
// @Description 删除 Rsync 模块 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Param name query string true "模块名称" | ||
// @Success 200 {object} controllers.SuccessResponse | ||
// @Router /plugins/rsync/modules [delete] | ||
func (r *RsyncController) Delete(ctx http.Context) http.Response { | ||
name := ctx.Request().Input("name") | ||
if len(name) == 0 { | ||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "name 不能为空") | ||
} | ||
|
||
config, err := tools.Read("/etc/rsyncd.conf") | ||
if err != nil { | ||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) | ||
} | ||
if !strings.Contains(config, "["+name+"]") { | ||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+name+" 不存在") | ||
} | ||
|
||
module := tools.Cut(config, "# "+name+"-START", "# "+name+"-END") | ||
config = strings.Replace(config, "\n# "+name+"-START"+module+"# "+name+"-END", "", -1) | ||
|
||
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()) | ||
} | ||
|
||
return controllers.Success(ctx, nil) | ||
} | ||
|
||
// Update | ||
// | ||
// @Summary 更新模块 | ||
// @Description 更新 Rsync 模块 | ||
// @Tags 插件-Rsync | ||
// @Produce json | ||
// @Security BearerToken | ||
// @Param data body requests.Update true "request" | ||
// @Success 200 {object} controllers.SuccessResponse | ||
// @Router /plugins/rsync/modules [put] | ||
func (r *RsyncController) Update(ctx http.Context) http.Response { | ||
var updateRequest requests.Update | ||
sanitize := controllers.Sanitize(ctx, &updateRequest) | ||
if sanitize != nil { | ||
return sanitize | ||
} | ||
|
||
config, err := tools.Read("/etc/rsyncd.conf") | ||
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+" 不存在") | ||
} | ||
|
||
newConf := `# ` + updateRequest.Name + `-START | ||
[` + updateRequest.Name + `] | ||
path = ` + updateRequest.Path + ` | ||
comment = ` + updateRequest.Comment + ` | ||
read only = no | ||
auth users = ` + updateRequest.AuthUser + ` | ||
hosts allow = ` + updateRequest.HostsAllow + ` | ||
secrets file = /etc/rsyncd.secrets | ||
# ` + 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) | ||
|
||
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 { | ||
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()) | ||
} | ||
|
||
return controllers.Success(ctx, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package requests | ||
|
||
import ( | ||
"github.com/goravel/framework/contracts/http" | ||
"github.com/goravel/framework/contracts/validation" | ||
) | ||
|
||
type Add struct { | ||
Name string `form:"name" json:"name"` | ||
Path string `form:"path" json:"path"` | ||
Comment string `form:"comment" json:"comment"` | ||
AuthUser string `form:"auth_user" json:"auth_user"` | ||
Secret string `form:"secret" json:"secret"` | ||
HostsAllow string `form:"hosts_allow" json:"hosts_allow"` | ||
} | ||
|
||
func (r *Add) Authorize(ctx http.Context) error { | ||
return nil | ||
} | ||
|
||
func (r *Add) 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_-]+)*$", | ||
"comment": "string", | ||
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$", | ||
"secret": "required|min_len:8", | ||
"hosts_allow": "string", | ||
} | ||
} | ||
|
||
func (r *Add) Messages(ctx http.Context) map[string]string { | ||
return map[string]string{} | ||
} | ||
|
||
func (r *Add) Attributes(ctx http.Context) map[string]string { | ||
return map[string]string{} | ||
} | ||
|
||
func (r *Add) PrepareForValidation(ctx http.Context, data validation.Data) error { | ||
return nil | ||
} |
Oops, something went wrong.