diff --git a/app/http/controllers/plugins/mysql_controller.go b/app/http/controllers/plugins/mysql_controller.go index a19d063b..53c63e26 100644 --- a/app/http/controllers/plugins/mysql_controller.go +++ b/app/http/controllers/plugins/mysql_controller.go @@ -1,7 +1,6 @@ package plugins import ( - "database/sql" "fmt" "regexp" @@ -12,6 +11,7 @@ import ( "github.com/TheTNB/panel/app/models" "github.com/TheTNB/panel/internal" "github.com/TheTNB/panel/internal/services" + "github.com/TheTNB/panel/pkg/db" "github.com/TheTNB/panel/pkg/io" "github.com/TheTNB/panel/pkg/shell" "github.com/TheTNB/panel/pkg/str" @@ -71,7 +71,7 @@ func (r *MySQLController) Load(ctx http.Context) http.Response { return controllers.Success(ctx, []types.NV{}) } - raw, err := shell.Execf("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1") + raw, err := shell.Execf("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1") if err != nil { return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败") } @@ -175,80 +175,45 @@ func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response { // SetRootPassword 设置root密码 func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response { - status, err := systemctl.Status("mysqld") - if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败") - } - if !status { - return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行") - } - rootPassword := ctx.Request().Input("password") if len(rootPassword) == 0 { return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码不能为空") } oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) - if oldRootPassword != rootPassword { - if _, err = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, oldRootPassword, rootPassword)); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置root密码失败: %v", err)) + mysql, err := db.NewMySQL("root", oldRootPassword, r.getSock(), "unix") + if err != nil { + // 尝试安全模式直接改密 + if err = db.MySQLResetRootPassword(rootPassword); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if _, err = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, rootPassword)); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败") - } - if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil { - _, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, rootPassword, oldRootPassword)) - _, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, oldRootPassword)) - return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err)) + } else { + if err = mysql.UserPassword("root", rootPassword); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } } + if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err)) + } return controllers.Success(ctx, nil) } // DatabaseList 获取数据库列表 func (r *MySQLController) DatabaseList(ctx http.Context) http.Response { - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) - type database struct { - Name string `json:"name"` - } - - db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/") + password := r.setting.Get(models.SettingKeyMysqlRootPassword) + mysql, err := db.NewMySQL("root", password, r.getSock(), "unix") if err != nil { return controllers.Success(ctx, http.Json{ "total": 0, - "items": []database{}, - }) - } - defer db.Close() - - if err = db.Ping(); err != nil { - return controllers.Success(ctx, http.Json{ - "total": 0, - "items": []database{}, + "items": []types.MySQLDatabase{}, }) } - rows, err := db.Query("SHOW DATABASES") + databases, err := mysql.Databases() if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) - } - defer rows.Close() - - var databases []database - for rows.Next() { - var d database - if err = rows.Scan(&d.Name); err != nil { - continue - } - - databases = append(databases, d) - } - - if err = rows.Err(); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败") } - paged, total := controllers.Paginate(ctx, databases) return controllers.Success(ctx, http.Json{ @@ -272,17 +237,18 @@ func (r *MySQLController) AddDatabase(ctx http.Context) http.Response { user := ctx.Request().Input("user") password := ctx.Request().Input("password") - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix") + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + if err = mysql.DatabaseCreate(database); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + if err = mysql.UserCreate(user, password); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + if err = mysql.PrivilegesGrant(user, database); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } return controllers.Success(ctx, nil) @@ -298,8 +264,12 @@ func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response { rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) database := ctx.Request().Input("database") - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix") + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) + } + if err = mysql.DatabaseDrop(database); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } return controllers.Success(ctx, nil) @@ -394,71 +364,20 @@ func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response { // UserList 用户列表 func (r *MySQLController) UserList(ctx http.Context) http.Response { - type user struct { - User string `json:"user"` - Host string `json:"host"` - Grants []string `json:"grants"` - } - - rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) - db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/") + password := r.setting.Get(models.SettingKeyMysqlRootPassword) + mysql, err := db.NewMySQL("root", password, r.getSock(), "unix") if err != nil { return controllers.Success(ctx, http.Json{ "total": 0, - "items": []user{}, - }) - } - defer db.Close() - - if err = db.Ping(); err != nil { - return controllers.Success(ctx, http.Json{ - "total": 0, - "items": []user{}, + "items": []types.MySQLUser{}, }) } - rows, err := db.Query("SELECT user, host FROM mysql.user") + users, err := mysql.Users() if err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) - } - defer rows.Close() - - var userGrants []user - - for rows.Next() { - var u user - if err = rows.Scan(&u.User, &u.Host); err != nil { - continue - } - - // 查询用户权限 - grantsRows, err := db.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", u.User, u.Host)) - if err != nil { - continue - } - defer grantsRows.Close() - - for grantsRows.Next() { - var grant string - if err = grantsRows.Scan(&grant); err != nil { - continue - } - - u.Grants = append(u.Grants, grant) - } - - if err = grantsRows.Err(); err != nil { - continue - } - - userGrants = append(userGrants, u) - } - - if err = rows.Err(); err != nil { return controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败") } - - paged, total := controllers.Paginate(ctx, userGrants) + paged, total := controllers.Paginate(ctx, users) return controllers.Success(ctx, http.Json{ "total": total, @@ -480,14 +399,15 @@ func (r *MySQLController) AddUser(ctx http.Context) http.Response { user := ctx.Request().Input("user") password := ctx.Request().Input("password") database := ctx.Request().Input("database") - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix") + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + if err = mysql.UserCreate(user, password); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + if err = mysql.PrivilegesGrant(user, database); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } return controllers.Success(ctx, nil) @@ -503,8 +423,12 @@ func (r *MySQLController) DeleteUser(ctx http.Context) http.Response { rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) user := ctx.Request().Input("user") - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix") + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) + } + if err = mysql.UserDrop(user); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } return controllers.Success(ctx, nil) @@ -522,11 +446,12 @@ func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response { rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) user := ctx.Request().Input("user") password := ctx.Request().Input("password") - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix") + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + if err = mysql.UserPassword(user, password); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } return controllers.Success(ctx, nil) @@ -544,15 +469,38 @@ func (r *MySQLController) SetUserPrivileges(ctx http.Context) http.Response { rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword) user := ctx.Request().Input("user") database := ctx.Request().Input("database") - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix") + if err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) - } - if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil { - return controllers.Error(ctx, http.StatusInternalServerError, out) + if err = mysql.PrivilegesGrant(user, database); err != nil { + return controllers.Error(ctx, http.StatusInternalServerError, err.Error()) } return controllers.Success(ctx, nil) } + +// getSock 获取sock文件位置 +func (r *MySQLController) getSock() string { + if io.Exists("/tmp/mysql.sock") { + return "/tmp/mysql.sock" + } + if io.Exists("/www/server/mysql/config/my.cnf") { + config, _ := io.Read("/www/server/mysql/config/my.cnf") + re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)\1`) + matches := re.FindStringSubmatch(config) + if len(matches) > 2 { + return matches[2] + } + } + if io.Exists("/etc/my.cnf") { + config, _ := io.Read("/etc/my.cnf") + re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)\1`) + matches := re.FindStringSubmatch(config) + if len(matches) > 2 { + return matches[2] + } + } + + return "/tmp/mysql.sock" +} diff --git a/pkg/db/mysql.go b/pkg/db/mysql.go new file mode 100644 index 00000000..683098ef --- /dev/null +++ b/pkg/db/mysql.go @@ -0,0 +1,173 @@ +package db + +import ( + "database/sql" + "fmt" + + _ "github.com/go-sql-driver/mysql" + + "github.com/TheTNB/panel/pkg/types" +) + +type MySQL struct { + db *sql.DB + username string + password string + address string +} + +func NewMySQL(username, password, address string, typ ...string) (*MySQL, error) { + dsn := fmt.Sprintf("%s:%s@tcp(%s)/", username, password, address) + if len(typ) > 0 && typ[0] == "unix" { + dsn = fmt.Sprintf("%s:%s@unix(%s)/", username, password, address) + } + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, fmt.Errorf("初始化MySQL连接失败: %w", err) + } + if db.Ping() != nil { + return nil, fmt.Errorf("连接MySQL失败: %w", err) + } + return &MySQL{ + db: db, + username: username, + password: password, + address: address, + }, nil +} + +func (m *MySQL) Close() error { + return m.db.Close() +} + +func (m *MySQL) Ping() error { + return m.db.Ping() +} + +func (m *MySQL) Query(query string, args ...any) (*sql.Rows, error) { + return m.db.Query(query, args...) +} + +func (m *MySQL) QueryRow(query string, args ...any) *sql.Row { + return m.db.QueryRow(query, args...) +} + +func (m *MySQL) Exec(query string, args ...any) (sql.Result, error) { + return m.db.Exec(query, args...) +} + +func (m *MySQL) Prepare(query string) (*sql.Stmt, error) { + return m.db.Prepare(query) +} + +func (m *MySQL) DatabaseCreate(name string) error { + _, err := m.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", name)) + m.flushPrivileges() + return err +} + +func (m *MySQL) DatabaseDrop(name string) error { + _, err := m.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", name)) + m.flushPrivileges() + return err +} + +func (m *MySQL) UserCreate(user, password string) error { + _, err := m.Exec(fmt.Sprintf("CREATE USER IF NOT EXISTS '%s'@'localhost' IDENTIFIED BY '%s'", user, password)) + m.flushPrivileges() + return err +} + +func (m *MySQL) UserDrop(user string) error { + _, err := m.Exec(fmt.Sprintf("DROP USER IF EXISTS '%s'@'localhost'", user)) + m.flushPrivileges() + return err +} + +func (m *MySQL) UserPassword(user, password string) error { + _, err := m.Exec(fmt.Sprintf("ALTER USER '%s'@'localhost' IDENTIFIED BY '%s'", user, password)) + m.flushPrivileges() + return err +} + +func (m *MySQL) PrivilegesGrant(user, database string) error { + _, err := m.Exec(fmt.Sprintf("GRANT ALL PRIVILEGES ON %s.* TO '%s'@'localhost'", database, user)) + m.flushPrivileges() + return err +} + +func (m *MySQL) PrivilegesRevoke(user, database string) error { + _, err := m.Exec(fmt.Sprintf("REVOKE ALL PRIVILEGES ON %s.* FROM '%s'@'localhost'", database, user)) + m.flushPrivileges() + return err +} + +func (m *MySQL) Users() ([]types.MySQLUser, error) { + rows, err := m.Query("SELECT user, host FROM mysql.user") + if err != nil { + return nil, err + } + defer rows.Close() + + var users []types.MySQLUser + for rows.Next() { + var user, host string + if err := rows.Scan(&user, &host); err != nil { + continue + } + grants, err := m.userGrants(user, host) + if err != nil { + continue + } + + users = append(users, types.MySQLUser{ + User: user, + Host: host, + Grants: grants, + }) + } + + return users, nil +} + +func (m *MySQL) Databases() ([]types.MySQLDatabase, error) { + rows, err := m.Query("SHOW DATABASES") + if err != nil { + return nil, err + } + defer rows.Close() + + var databases []types.MySQLDatabase + for rows.Next() { + var database string + if err := rows.Scan(&database); err != nil { + continue + } + databases = append(databases, types.MySQLDatabase{ + Name: database, + }) + } + return databases, nil +} + +func (m *MySQL) userGrants(user, host string) ([]string, error) { + rows, err := m.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", user, host)) + if err != nil { + return nil, err + } + defer rows.Close() + + var grants []string + for rows.Next() { + var grant string + if err := rows.Scan(&grant); err != nil { + continue + } + grants = append(grants, grant) + } + return grants, nil +} + +func (m *MySQL) flushPrivileges() { + _, _ = m.Exec("FLUSH PRIVILEGES") +} diff --git a/pkg/db/mysql_tools.go b/pkg/db/mysql_tools.go new file mode 100644 index 00000000..cb8201cb --- /dev/null +++ b/pkg/db/mysql_tools.go @@ -0,0 +1,33 @@ +package db + +import ( + "errors" + "fmt" + + "github.com/TheTNB/panel/pkg/shell" + "github.com/TheTNB/panel/pkg/systemctl" +) + +// MySQLResetRootPassword 重置 MySQL root密码 +func MySQLResetRootPassword(password string) error { + _ = systemctl.Stop("mysqld") + if run, err := systemctl.Status("mysqld"); err != nil || run { + return fmt.Errorf("停止MySQL失败: %w", err) + } + _, _ = shell.Execf(`systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"`) + if err := systemctl.Start("mysqld"); err != nil { + return fmt.Errorf("以安全模式启动MySQL失败: %w", err) + } + if _, err := shell.Execf(`mysql -uroot -e "FLUSH PRIVILEGES;UPDATE mysql.user SET authentication_string=null WHERE user='root' AND host='localhost';ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';FLUSH PRIVILEGES;"`, password); err != nil { + return errors.New("设置root密码失败") + } + if err := systemctl.Stop("mysqld"); err != nil { + return fmt.Errorf("停止MySQL失败: %w", err) + } + _, _ = shell.Execf(`systemctl unset-environment MYSQLD_OPTS`) + if err := systemctl.Start("mysqld"); err != nil { + return fmt.Errorf("启动MySQL失败: %w", err) + } + + return nil +} diff --git a/pkg/types/mysql.go b/pkg/types/mysql.go new file mode 100644 index 00000000..a5381dbf --- /dev/null +++ b/pkg/types/mysql.go @@ -0,0 +1,11 @@ +package types + +type MySQLUser struct { + User string `json:"user"` + Host string `json:"host"` + Grants []string `json:"grants"` +} + +type MySQLDatabase struct { + Name string `json:"name"` +} diff --git a/scripts/mysql/install.sh b/scripts/mysql/install.sh index 7192372d..63b379f9 100644 --- a/scripts/mysql/install.sh +++ b/scripts/mysql/install.sh @@ -287,8 +287,8 @@ chmod 644 ${mysqlPath}/conf/my.cnf ${mysqlPath}/bin/mysqld --initialize-insecure --user=mysql --basedir=${mysqlPath} --datadir=${mysqlPath}/data -echo "export PATH=${mysqlPath}/bin:\$PATH" >> /etc/profile.d/mysql.sh -source /etc/profile +# 软链接 +ln -sf ${mysqlPath}/bin/* /usr/bin/ # 写入 systemd 配置 cat > /etc/systemd/system/mysqld.service << EOF diff --git a/scripts/mysql/uninstall.sh b/scripts/mysql/uninstall.sh index fd55117a..67845204 100644 --- a/scripts/mysql/uninstall.sh +++ b/scripts/mysql/uninstall.sh @@ -34,9 +34,6 @@ rm -f /usr/lib64/libmysql* userdel -r mysql groupdel mysql -rm -f /etc/profile.d/mysql.sh -source /etc/profile - panel deletePlugin mysql${1} echo -e "${HR}\nMySQL-${1} 卸载完成\n${HR}" diff --git a/scripts/mysql/update.sh b/scripts/mysql/update.sh index 432d8f67..aa1d127f 100644 --- a/scripts/mysql/update.sh +++ b/scripts/mysql/update.sh @@ -120,6 +120,9 @@ chown -R mysql:mysql ${mysqlPath} chmod -R 755 ${mysqlPath} chmod 644 ${mysqlPath}/conf/my.cnf +# 软链接 +ln -sf ${mysqlPath}/bin/* /usr/bin/ + # 启动服务 systemctl start mysqld diff --git a/scripts/update_panel.sh b/scripts/update_panel.sh index cff41b2a..770ffd1f 100644 --- a/scripts/update_panel.sh +++ b/scripts/update_panel.sh @@ -133,6 +133,16 @@ if version_lt "$oldVersion" "2.2.14"; then fi fi +if version_lt "$oldVersion" "2.2.16"; then + echo "更新面板到 v2.2.16 ..." + echo "Update panel to v2.2.16 ..." + if [ -f "/www/server/mysql/bin/mysql" ]; then + ln -sf /www/server/mysql/bin/* /usr/bin/ + rm -f /etc/profile.d/mysql.sh + source /etc/profile + fi +fi + echo $HR echo "更新结束" echo "Update finished"