mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 05:31:44 +08:00
271 lines
7.0 KiB
Go
271 lines
7.0 KiB
Go
package rsync
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-rat/chix"
|
|
"github.com/go-rat/utils/str"
|
|
|
|
"github.com/tnb-labs/panel/internal/service"
|
|
"github.com/tnb-labs/panel/pkg/io"
|
|
"github.com/tnb-labs/panel/pkg/shell"
|
|
"github.com/tnb-labs/panel/pkg/systemctl"
|
|
)
|
|
|
|
type App struct{}
|
|
|
|
func NewApp() *App {
|
|
return &App{}
|
|
}
|
|
|
|
func (s *App) Route(r chi.Router) {
|
|
r.Get("/modules", s.List)
|
|
r.Post("/modules", s.Create)
|
|
r.Post("/modules/{name}", s.Update)
|
|
r.Delete("/modules/{name}", s.Delete)
|
|
r.Get("/config", s.GetConfig)
|
|
r.Post("/config", s.UpdateConfig)
|
|
}
|
|
|
|
func (s *App) List(w http.ResponseWriter, r *http.Request) {
|
|
config, err := io.Read("/etc/rsyncd.conf")
|
|
if err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
var modules []Module
|
|
lines := strings.Split(config, "\n")
|
|
var currentModule *Module
|
|
|
|
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]
|
|
currentModule = &Module{
|
|
Name: moduleName,
|
|
}
|
|
} 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
|
|
currentModule.Secret, err = shell.Execf(`grep -E '^%s:.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'`, currentModule.AuthUser)
|
|
if err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "获取模块%s的密钥失败", currentModule.AuthUser)
|
|
return
|
|
}
|
|
case "hosts allow":
|
|
currentModule.HostsAllow = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if currentModule != nil {
|
|
modules = append(modules, *currentModule)
|
|
}
|
|
|
|
paged, total := service.Paginate(r, modules)
|
|
|
|
service.Success(w, chix.M{
|
|
"total": total,
|
|
"items": paged,
|
|
})
|
|
}
|
|
|
|
func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
|
req, err := service.Bind[Create](r)
|
|
if err != nil {
|
|
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
config, err := io.Read("/etc/rsyncd.conf")
|
|
if err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
if strings.Contains(config, "["+req.Name+"]") {
|
|
service.Error(w, http.StatusUnprocessableEntity, "模块%s已存在", req.Name)
|
|
return
|
|
}
|
|
|
|
conf := `# ` + req.Name + `-START
|
|
[` + req.Name + `]
|
|
path = ` + req.Path + `
|
|
comment = ` + req.Comment + `
|
|
read only = no
|
|
auth users = ` + req.AuthUser + `
|
|
hosts allow = ` + req.HostsAllow + `
|
|
secrets file = /etc/rsyncd.secrets
|
|
# ` + req.Name + `-END
|
|
`
|
|
|
|
if err = io.WriteAppend("/etc/rsyncd.conf", conf, 0644); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart("rsyncd"); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
service.Success(w, nil)
|
|
}
|
|
|
|
func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
|
req, err := service.Bind[Delete](r)
|
|
if err != nil {
|
|
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
config, err := io.Read("/etc/rsyncd.conf")
|
|
if err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
if !strings.Contains(config, "["+req.Name+"]") {
|
|
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
|
|
return
|
|
}
|
|
|
|
module := str.Cut(config, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
|
config = strings.Replace(config, "\n# "+req.Name+"-START"+module+"# "+req.Name+"-END", "", -1)
|
|
|
|
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
|
|
if len(match) == 2 {
|
|
authUser := match[1]
|
|
if _, err = shell.Execf(`sed -i '/^%s:.*$/d' /etc/rsyncd.secrets`, authUser); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err = io.Write("/etc/rsyncd.conf", config, 0644); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart("rsyncd"); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
service.Success(w, nil)
|
|
}
|
|
|
|
func (s *App) Update(w http.ResponseWriter, r *http.Request) {
|
|
req, err := service.Bind[Update](r)
|
|
if err != nil {
|
|
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
config, err := io.Read("/etc/rsyncd.conf")
|
|
if err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
if !strings.Contains(config, "["+req.Name+"]") {
|
|
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
|
|
return
|
|
}
|
|
|
|
newConf := `# ` + req.Name + `-START
|
|
[` + req.Name + `]
|
|
path = ` + req.Path + `
|
|
comment = ` + req.Comment + `
|
|
read only = no
|
|
auth users = ` + req.AuthUser + `
|
|
hosts allow = ` + req.HostsAllow + `
|
|
secrets file = /etc/rsyncd.secrets
|
|
# ` + req.Name + `-END`
|
|
|
|
module := str.Cut(config, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
|
config = strings.Replace(config, "# "+req.Name+"-START"+module+"# "+req.Name+"-END", newConf, -1)
|
|
|
|
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
|
|
if len(match) == 2 {
|
|
authUser := match[1]
|
|
if _, err = shell.Execf(`sed -i '/^%s:.*$/d' /etc/rsyncd.secrets`, authUser); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err = io.Write("/etc/rsyncd.conf", config, 0644); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart("rsyncd"); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
service.Success(w, nil)
|
|
}
|
|
|
|
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
|
config, err := io.Read("/etc/rsyncd.conf")
|
|
if err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
service.Success(w, config)
|
|
}
|
|
|
|
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
|
req, err := service.Bind[UpdateConfig](r)
|
|
if err != nil {
|
|
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
if err = io.Write("/etc/rsyncd.conf", req.Config, 0644); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart("rsyncd"); err != nil {
|
|
service.Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
service.Success(w, nil)
|
|
}
|