diff --git a/app/http/controllers/plugins/postgresql16/postgresql16_controller.go b/app/http/controllers/plugins/postgresql16/postgresql16_controller.go new file mode 100644 index 00000000..f69d08be --- /dev/null +++ b/app/http/controllers/plugins/postgresql16/postgresql16_controller.go @@ -0,0 +1,611 @@ +package postgresql16 + +import ( + "strings" + + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/facades" + "github.com/goravel/framework/support/carbon" + + "panel/app/http/controllers" + "panel/app/models" + "panel/app/services" + "panel/pkg/tools" +) + +type Postgresql16Controller struct { + setting services.Setting + backup services.Backup +} + +type Info struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func NewPostgresql16Controller() *Postgresql16Controller { + return &Postgresql16Controller{ + setting: services.NewSettingImpl(), + backup: services.NewBackupImpl(), + } +} + +// Status 获取运行状态 +func (c *Postgresql16Controller) Status(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") + } + + if status == "active" { + return controllers.Success(ctx, true) + } else { + return controllers.Success(ctx, false) + } +} + +// Reload 重载配置 +func (c *Postgresql16Controller) Reload(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + tools.Exec("systemctl reload postgresql") + status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") + } + + if status == "active" { + return controllers.Success(ctx, true) + } else { + return controllers.Success(ctx, false) + } +} + +// Restart 重启服务 +func (c *Postgresql16Controller) Restart(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + tools.Exec("systemctl restart postgresql") + status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") + } + + if status == "active" { + return controllers.Success(ctx, true) + } else { + return controllers.Success(ctx, false) + } +} + +// Start 启动服务 +func (c *Postgresql16Controller) Start(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + tools.Exec("systemctl start postgresql") + status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") + } + + if status == "active" { + return controllers.Success(ctx, true) + } else { + return controllers.Success(ctx, false) + } +} + +// Stop 停止服务 +func (c *Postgresql16Controller) Stop(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + tools.Exec("systemctl stop postgresql") + status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'") + if len(status) == 0 { + return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败") + } + + if status != "active" { + return controllers.Success(ctx, true) + } else { + return controllers.Success(ctx, false) + } +} + +// GetConfig 获取配置 +func (c *Postgresql16Controller) GetConfig(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + // 获取配置 + config := tools.Read("/www/server/postgresql/data/postgresql.conf") + if len(config) == 0 { + return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败") + } + + return controllers.Success(ctx, config) +} + +// GetUserConfig 获取用户配置 +func (c *Postgresql16Controller) GetUserConfig(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + // 获取配置 + config := tools.Read("/www/server/postgresql/data/pg_hba.conf") + if len(config) == 0 { + return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败") + } + + return controllers.Success(ctx, config) +} + +// SaveConfig 保存配置 +func (c *Postgresql16Controller) SaveConfig(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + config := ctx.Request().Input("config") + if len(config) == 0 { + return controllers.Error(ctx, http.StatusBadRequest, "配置不能为空") + } + + if !tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644) { + return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败") + } + + return c.Restart(ctx) +} + +// SaveUserConfig 保存用户配置 +func (c *Postgresql16Controller) SaveUserConfig(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + config := ctx.Request().Input("config") + if len(config) == 0 { + return controllers.Error(ctx, http.StatusBadRequest, "配置不能为空") + } + + if !tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644) { + return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败") + } + + return c.Restart(ctx) +} + +// Load 获取负载 +func (c *Postgresql16Controller) Load(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'") + if status != "active" { + return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行") + } + + data := []Info{ + {"启动时间", carbon.Parse(tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)).ToDateTimeString()}, + {"进程 PID", tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)}, + {"进程数", tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)}, + {"总连接数", tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)}, + {"空间占用", tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)}, + } + + return controllers.Success(ctx, data) +} + +// Log 获取日志 +func (c *Postgresql16Controller) Log(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + log := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log") + return controllers.Success(ctx, log) +} + +// ClearLog 清空日志 +func (c *Postgresql16Controller) ClearLog(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log") + return controllers.Success(ctx, nil) +} + +// DatabaseList 获取数据库列表 +func (c *Postgresql16Controller) DatabaseList(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'") + if status != "active" { + return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行") + } + + raw := tools.Exec(`echo "\l" | su - postgres -c "psql"`) + databases := strings.Split(raw, "\n") + databases = databases[3 : len(databases)-1] + + type database struct { + Name string `json:"name"` + Owner string `json:"owner"` + Encoding string `json:"encoding"` + } + + var databaseList []database + for _, db := range databases { + parts := strings.Split(db, "|") + if len(parts) != 8 || len(strings.TrimSpace(parts[0])) == 0 { + continue + } + + databaseList = append(databaseList, database{ + Name: strings.TrimSpace(parts[0]), + Owner: strings.TrimSpace(parts[1]), + Encoding: strings.TrimSpace(parts[2]), + }) + } + + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(databaseList) { + return controllers.Success(ctx, http.Json{ + "total": 0, + "items": []database{}, + }) + } + if endIndex > len(databaseList) { + endIndex = len(databaseList) + } + pagedDatabases := databaseList[startIndex:endIndex] + + return controllers.Success(ctx, http.Json{ + "total": len(databaseList), + "items": pagedDatabases, + }) +} + +// AddDatabase 添加数据库 +func (c *Postgresql16Controller) AddDatabase(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "password": "required|min_len:8|max_len:255", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + database := ctx.Request().Input("database") + user := ctx.Request().Input("user") + password := ctx.Request().Input("password") + + tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`) + tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`) + tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`) + + userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256" + tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`) + + return c.Reload(ctx) +} + +// DeleteDatabase 删除数据库 +func (c *Postgresql16Controller) DeleteDatabase(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:postgres,template0,template1", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + database := ctx.Request().Input("database") + tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`) + + return controllers.Success(ctx, nil) +} + +// BackupList 获取备份列表 +func (c *Postgresql16Controller) BackupList(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + backupList, err := c.backup.PostgresqlList() + if err != nil { + facades.Log().Error("[PostgreSQL] 获取备份列表失败:" + err.Error()) + return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败") + } + + return controllers.Success(ctx, backupList) +} + +// UploadBackup 上传备份 +func (c *Postgresql16Controller) UploadBackup(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + file, err := ctx.Request().File("file") + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, "上传文件失败") + } + + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/postgresql" + if !tools.Exists(backupPath) { + tools.Mkdir(backupPath, 0644) + } + + name := file.GetClientOriginalName() + _, err = file.StoreAs(backupPath, name) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, "上传文件失败") + } + + return controllers.Success(ctx, nil) +} + +// CreateBackup 创建备份 +func (c *Postgresql16Controller) CreateBackup(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + database := ctx.Request().Input("database") + err = c.backup.PostgresqlBackup(database) + if err != nil { + facades.Log().Error("[PostgreSQL] 创建备份失败:" + err.Error()) + return controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败") + } + + return controllers.Success(ctx, nil) +} + +// DeleteBackup 删除备份 +func (c *Postgresql16Controller) DeleteBackup(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "name": "required|min_len:1|max_len:255", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/postgresql" + fileName := ctx.Request().Input("name") + tools.Remove(backupPath + "/" + fileName) + + return controllers.Success(ctx, nil) +} + +// RestoreBackup 还原备份 +func (c *Postgresql16Controller) RestoreBackup(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "name": "required|min_len:1|max_len:255", + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + err = c.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("name")) + if err != nil { + facades.Log().Error("[PostgreSQL] 还原失败:" + err.Error()) + return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error()) + } + + return controllers.Success(ctx, nil) +} + +// UserList 用户列表 +func (c *Postgresql16Controller) UserList(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + type user struct { + User string `json:"user"` + Role string `json:"role"` + } + + raw := tools.Exec(`echo "\du" | su - postgres -c "psql"`) + users := strings.Split(raw, "\n") + if len(users) < 4 { + return controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空") + } + users = users[3:] + + var userList []user + for _, u := range users { + userInfo := strings.Split(u, "|") + if len(userInfo) != 3 { + continue + } + + userList = append(userList, user{ + User: strings.TrimSpace(userInfo[0]), + Role: strings.TrimSpace(userInfo[1]), + }) + } + + page := ctx.Request().QueryInt("page", 1) + limit := ctx.Request().QueryInt("limit", 10) + startIndex := (page - 1) * limit + endIndex := page * limit + if startIndex > len(userList) { + return controllers.Success(ctx, http.Json{ + "total": 0, + "items": []user{}, + }) + } + if endIndex > len(userList) { + endIndex = len(userList) + } + pagedUsers := userList[startIndex:endIndex] + + return controllers.Success(ctx, http.Json{ + "total": len(userList), + "items": pagedUsers, + }) +} + +// AddUser 添加用户 +func (c *Postgresql16Controller) AddUser(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "password": "required|min_len:8|max_len:255", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + user := ctx.Request().Input("user") + password := ctx.Request().Input("password") + database := ctx.Request().Input("database") + tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`) + tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`) + + userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256" + tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`) + + return c.Reload(ctx) +} + +// DeleteUser 删除用户 +func (c *Postgresql16Controller) DeleteUser(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + user := ctx.Request().Input("user") + tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`) + tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`) + + return c.Reload(ctx) +} + +// SetUserPassword 设置用户密码 +func (c *Postgresql16Controller) SetUserPassword(ctx http.Context) http.Response { + check := controllers.Check(ctx, "postgresql16") + if check != nil { + return check + } + + validator, err := ctx.Request().Validate(map[string]string{ + "user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$", + "password": "required|min_len:8|max_len:255", + }) + if err != nil { + return controllers.Error(ctx, http.StatusBadRequest, err.Error()) + } + if validator.Fails() { + return controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One()) + } + + user := ctx.Request().Input("user") + password := ctx.Request().Input("password") + tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`) + + return controllers.Success(ctx, nil) +} diff --git a/app/plugins/postgresql15/postgresql15.go b/app/plugins/postgresql15/postgresql15.go index 3e4fe271..528d7d6e 100644 --- a/app/plugins/postgresql15/postgresql15.go +++ b/app/plugins/postgresql15/postgresql15.go @@ -4,9 +4,9 @@ var ( Name = "PostgreSQL-15" Description = "PostgreSQL 是开源的对象 - 关系数据库数据库管理系统,在类似 BSD 许可与 MIT 许可的 PostgreSQL 许可下发行。" Slug = "postgresql15" - Version = "15.3" + Version = "15.4" Requires = []string{} - Excludes = []string{} + Excludes = []string{"postgresql16"} Install = `bash /www/panel/scripts/postgresql/install.sh 15` Uninstall = `bash /www/panel/scripts/postgresql/uninstall.sh 15` Update = `bash /www/panel/scripts/postgresql/update.sh 15` diff --git a/app/plugins/postgresql16/postgresql16.go b/app/plugins/postgresql16/postgresql16.go new file mode 100644 index 00000000..32b4fcd7 --- /dev/null +++ b/app/plugins/postgresql16/postgresql16.go @@ -0,0 +1,13 @@ +package postgresql16 + +var ( + Name = "PostgreSQL-16" + Description = "PostgreSQL 是开源的对象 - 关系数据库数据库管理系统,在类似 BSD 许可与 MIT 许可的 PostgreSQL 许可下发行。" + Slug = "postgresql16" + Version = "16.0" + Requires = []string{} + Excludes = []string{"postgresql15"} + Install = `bash /www/panel/scripts/postgresql/install.sh 16` + Uninstall = `bash /www/panel/scripts/postgresql/uninstall.sh 16` + Update = `bash /www/panel/scripts/postgresql/update.sh 16` +) diff --git a/app/services/plugin.go b/app/services/plugin.go index 93a22df5..31273425 100644 --- a/app/services/plugin.go +++ b/app/services/plugin.go @@ -15,6 +15,7 @@ import ( "panel/app/plugins/php82" "panel/app/plugins/phpmyadmin" "panel/app/plugins/postgresql15" + "panel/app/plugins/postgresql16" "panel/app/plugins/pureftpd" "panel/app/plugins/redis" "panel/app/plugins/s3fs" @@ -107,6 +108,17 @@ func (r *PluginImpl) All() []PanelPlugin { Uninstall: postgresql15.Uninstall, Update: postgresql15.Update, }) + p = append(p, PanelPlugin{ + Name: postgresql16.Name, + Description: postgresql16.Description, + Slug: postgresql16.Slug, + Version: postgresql16.Version, + Requires: postgresql16.Requires, + Excludes: postgresql16.Excludes, + Install: postgresql16.Install, + Uninstall: postgresql16.Uninstall, + Update: postgresql16.Update, + }) p = append(p, PanelPlugin{ Name: php74.Name, Description: php74.Description, diff --git a/public/panel/views/plugins/postgresql16.html b/public/panel/views/plugins/postgresql16.html new file mode 100644 index 00000000..77a48cc4 --- /dev/null +++ b/public/panel/views/plugins/postgresql16.html @@ -0,0 +1,524 @@ + +
+ 当前状态:获取中+
面板仅集成了部分常用功能,如需更多功能,请使用 + pgAdmin 客户端。 ++ +
此处修改的是PostgreSQL主配置文件,如果你不了解各参数的含义,请不要随意修改!+ +
+ 提示:Ctrl+F 搜索关键字,Ctrl+S 保存,Ctrl+H 查找替换! +
此处修改的是PostgreSQL用户配置文件,如果你不了解各参数的含义,请不要随意修改!+ +
+ 提示:Ctrl+F 搜索关键字,Ctrl+S 保存,Ctrl+H 查找替换! +
+ 获取中... ++