Files
panel/internal/apps/mongodb/app.go
T
2026-08-15 08:33:30 +08:00

263 lines
8.2 KiB
Go

package mongodb
import (
"encoding/json"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"go.yaml.in/yaml/v4"
"github.com/acepanel/panel/v3/internal/app"
"github.com/acepanel/panel/v3/internal/apps/common"
"github.com/acepanel/panel/v3/internal/biz"
"github.com/acepanel/panel/v3/internal/service"
"github.com/acepanel/panel/v3/pkg/io"
"github.com/acepanel/panel/v3/pkg/shell"
"github.com/acepanel/panel/v3/pkg/systemctl"
"github.com/acepanel/panel/v3/pkg/types"
)
type App struct {
t *gotext.Locale
settingRepo biz.SettingRepo
databaseServerRepo biz.DatabaseServerRepo
}
func NewApp(t *gotext.Locale, databaseServerRepo biz.DatabaseServerRepo, settingRepo biz.SettingRepo) *App {
return &App{
t: t,
settingRepo: settingRepo,
databaseServerRepo: databaseServerRepo,
}
}
func (s *App) Route(r chi.Router) {
r.Get("/load", s.Load)
r.Get("/config", s.GetConfig)
r.Post("/config", s.UpdateConfig)
r.Get("/config_tune", s.GetConfigTune)
r.Post("/config_tune", s.UpdateConfigTune)
r.Get("/admin_password", s.GetAdminPassword)
r.Post("/admin_password", s.SetAdminPassword)
}
func (s *App) Status() string {
ok, _ := systemctl.Status("mongod")
return types.AggregateAppStatus(ok)
}
// Load 获取 MongoDB 运行状态
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, _ := systemctl.Status("mongod")
if !status {
service.Success(w, []types.NV{})
return
}
password, _ := s.settingRepo.Get(biz.SettingKeyMongoDBAdminPassword)
raw, err := shell.Execf(`mongosh --quiet --eval "JSON.stringify(db.serverStatus())" -u admin -p '%s' --authenticationDatabase admin 2>/dev/null`, password)
if err != nil {
service.Success(w, []types.NV{})
return
}
var status2 struct {
Uptime int `json:"uptime"`
Connections struct {
Current int `json:"current"`
TotalCreated int `json:"totalCreated"`
} `json:"connections"`
OpCounters struct {
Query int `json:"query"`
Insert int `json:"insert"`
Update int `json:"update"`
Delete int `json:"delete"`
} `json:"opcounters"`
Mem struct {
Resident int `json:"resident"`
} `json:"mem"`
StorageEngine struct {
Name string `json:"name"`
} `json:"storageEngine"`
Version string `json:"version"`
}
if err = json.Unmarshal([]byte(raw), &status2); err != nil {
service.Success(w, []types.NV{})
return
}
data := []types.NV{
{Name: s.t.Get("Version"), Value: status2.Version},
{Name: s.t.Get("Uptime (seconds)"), Value: cast.ToString(status2.Uptime)},
{Name: s.t.Get("Current Connections"), Value: cast.ToString(status2.Connections.Current)},
{Name: s.t.Get("Total Connections Created"), Value: cast.ToString(status2.Connections.TotalCreated)},
{Name: s.t.Get("Query Operations"), Value: cast.ToString(status2.OpCounters.Query)},
{Name: s.t.Get("Insert Operations"), Value: cast.ToString(status2.OpCounters.Insert)},
{Name: s.t.Get("Update Operations"), Value: cast.ToString(status2.OpCounters.Update)},
{Name: s.t.Get("Delete Operations"), Value: cast.ToString(status2.OpCounters.Delete)},
{Name: s.t.Get("Resident Memory (MB)"), Value: cast.ToString(status2.Mem.Resident)},
{Name: s.t.Get("Storage Engine"), Value: status2.StorageEngine.Name},
}
service.Success(w, data)
}
// GetConfig 获取配置
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
common.ServeConfig(w, s.configPath())
}
// UpdateConfig 更新配置
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
common.SaveConfig(w, r, s.configPath(), "mongod")
}
// GetConfigTune 获取配置调整参数
func (s *App) GetConfigTune(w http.ResponseWriter, r *http.Request) {
raw, _ := io.Read(s.configPath())
var cfg map[string]any
_ = yaml.Unmarshal([]byte(raw), &cfg)
if cfg == nil {
cfg = make(map[string]any)
}
tune := ConfigTune{
DbPath: s.getYAMLValue(cfg, "storage.dbPath"),
CacheSizeGB: s.getYAMLValue(cfg, "storage.wiredTiger.engineConfig.cacheSizeGB"),
Port: s.getYAMLValue(cfg, "net.port"),
BindIp: s.getYAMLValue(cfg, "net.bindIp"),
SystemLogPath: s.getYAMLValue(cfg, "systemLog.path"),
Authorization: s.getYAMLValue(cfg, "security.authorization"),
}
service.Success(w, tune)
}
// UpdateConfigTune 更新配置调整参数
func (s *App) UpdateConfigTune(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[ConfigTune](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
raw, _ := io.Read(s.configPath())
var cfg map[string]any
if err = yaml.Unmarshal([]byte(raw), &cfg); err != nil {
cfg = make(map[string]any)
}
s.setNestedYAMLValue(cfg, "storage.dbPath", req.DbPath)
s.setNestedYAMLValue(cfg, "storage.wiredTiger.engineConfig.cacheSizeGB", req.CacheSizeGB)
s.setNestedYAMLValue(cfg, "net.port", req.Port)
s.setNestedYAMLValue(cfg, "net.bindIp", req.BindIp)
s.setNestedYAMLValue(cfg, "systemLog.path", req.SystemLogPath)
s.setNestedYAMLValue(cfg, "security.authorization", req.Authorization)
data, err := yaml.Marshal(cfg)
if err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Write(s.configPath(), string(data), 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("mongod"); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
}
// GetAdminPassword 获取 admin 密码
func (s *App) GetAdminPassword(w http.ResponseWriter, r *http.Request) {
password, err := s.settingRepo.Get(biz.SettingKeyMongoDBAdminPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get MongoDB admin password: %v", err))
return
}
service.Success(w, password)
}
// SetAdminPassword 设置 admin 密码
func (s *App) SetAdminPassword(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[SetAdminPassword](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
oldPassword, _ := s.settingRepo.Get(biz.SettingKeyMongoDBAdminPassword)
// 尝试用旧密码连接修改
_, err = shell.Execf(`mongosh --quiet -u admin -p '%s' --authenticationDatabase admin --eval "db.changeUserPassword('admin', '%s')"`, oldPassword, req.Password)
if err != nil {
// 回退:停止服务,无认证模式修改
_ = systemctl.Stop("mongod")
_, _ = shell.Execf(`su -s /bin/bash mongod -c "mongod --config %s --noauth --fork --logpath /tmp/mongod_reset.log"`, s.configPath())
_, resetErr := shell.Execf(`mongosh --quiet --eval "db.getSiblingDB('admin').changeUserPassword('admin', '%s')"`, req.Password)
_, _ = shell.Execf(`su -s /bin/bash mongod -c "mongod --config %s --shutdown" 2>/dev/null; pkill -f 'mongod --config.*--noauth' 2>/dev/null`, s.configPath())
_ = systemctl.Start("mongod")
if resetErr != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set MongoDB admin password: %v", resetErr))
return
}
}
if err = s.settingRepo.Set(biz.SettingKeyMongoDBAdminPassword, req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to save MongoDB admin password: %v", err))
return
}
_ = s.databaseServerRepo.UpdatePassword("local_mongodb", req.Password)
service.Success(w, nil)
}
// configPath 返回配置文件路径
func (s *App) configPath() string {
return app.Root + "/server/mongodb/mongod.conf"
}
// getYAMLValue 获取嵌套 YAML 值,支持 dot notation
func (s *App) getYAMLValue(cfg map[string]any, key string) string {
parts := strings.SplitN(key, ".", 2)
val, ok := cfg[parts[0]]
if !ok {
return ""
}
if len(parts) == 1 {
return cast.ToString(val)
}
nested, ok := val.(map[string]any)
if !ok {
return ""
}
return s.getYAMLValue(nested, parts[1])
}
// setNestedYAMLValue 设置嵌套 YAML 值,逐层创建 map
func (s *App) setNestedYAMLValue(cfg map[string]any, key string, value string) {
if value == "" {
return
}
parts := strings.SplitN(key, ".", 2)
if len(parts) == 1 {
cfg[parts[0]] = value
return
}
nested, ok := cfg[parts[0]].(map[string]any)
if !ok {
nested = make(map[string]any)
cfg[parts[0]] = nested
}
s.setNestedYAMLValue(nested, parts[1], value)
}