package initialize import ( "database/sql" "embed" "fmt" "html/template" "log" "net/http" "os" "github.com/perfect-panel/server/internal/report" "github.com/perfect-panel/server/pkg/logger" "gorm.io/driver/mysql" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/perfect-panel/server/initialize/migrate" "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/pkg/conf" "github.com/perfect-panel/server/pkg/orm" "github.com/perfect-panel/server/pkg/tool" "github.com/pkg/errors" "gopkg.in/yaml.v3" "gorm.io/gorm" ) //go:embed templates/*.html var templateFS embed.FS var initStatus = make(chan bool) var configPath string func Config(path string) (chan bool, *http.Server) { // Set the configuration file path configPath = path // Create a new Gin instance r := gin.Default() // get server port port := 8080 host := "127.0.0.1" // check gateway mode if report.IsGatewayMode() { // get free port freePort, err := report.ModulePort() if err != nil { logger.Errorf("get module port error: %s", err.Error()) panic(err) } port = freePort // register module err = report.RegisterModule(port) if err != nil { logger.Errorf("register module error: %s", err.Error()) panic(err) } logger.Infof("module registered on port %d", port) } // Create a new HTTP server server := &http.Server{ Addr: fmt.Sprintf("%s:%d", host, port), Handler: r, } // Load templates tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html")) r.SetHTMLTemplate(tmpl) r.GET("/init", handleInit) r.POST("/init/config", handleInitConfig) r.POST("/init/mysql/test", HandleMySQLTest) r.POST("/init/redis/test", HandleRedisTest) // Handle 404 r.NoRoute(func(c *gin.Context) { c.Redirect(http.StatusFound, "/init") }) go func(server *http.Server) { // Start the server if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("listen: %s\n", err) } }(server) return initStatus, server } func handleInit(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil) } func handleInitConfig(c *gin.Context) { // Load configuration file var cfg config.File conf.MustLoad(configPath, &cfg) var request struct { AdminEmail string `json:"adminEmail"` AdminPassword string `json:"adminPassword"` MysqlHost string `json:"mysqlHost"` MysqlPort string `json:"mysqlPort"` MysqlDatabase string `json:"mysqlDatabase"` MysqlUser string `json:"mysqlUser"` MysqlPassword string `json:"mysqlPassword"` RedisHost string `json:"redisHost"` RedisPort string `json:"redisPort"` RedisPassword string `json:"redisPassword"` } if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "msg": "Invalid request", "data": nil, }) c.Abort() return } cfg.Debug = false // jwt secret cfg.JwtAuth.AccessSecret = uuid.New().String() // mysql cfg.MySQL.Addr = fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort) cfg.MySQL.Dbname = request.MysqlDatabase cfg.MySQL.Username = request.MysqlUser cfg.MySQL.Password = request.MysqlPassword // redis cfg.Redis.Host = fmt.Sprintf("%s:%s", request.RedisHost, request.RedisPort) cfg.Redis.Pass = request.RedisPassword // save config fileData, err := yaml.Marshal(cfg) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "Configuration initialization failed", "data": nil, }) c.Abort() return } // create mysql connection db, err := orm.ConnectMysql(orm.Mysql{ Config: orm.Config{ Addr: fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort), Username: request.MysqlUser, Password: request.MysqlPassword, Dbname: request.MysqlDatabase, Config: "charset%3Dutf8mb4%26parseTime%3Dtrue%26loc%3DLocal", MaxIdleConns: 10, MaxOpenConns: 10, SlowThreshold: 1000, }, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "MySQL connection failed", "data": nil, }) c.Abort() return } dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.MysqlUser, request.MysqlPassword, request.MysqlHost, request.MysqlPort, request.MysqlDatabase) // migrate database if err = migrate.Migrate(dsn).Up(); err != nil { logger.Errorf("[Init Mysql] Migrate failed: %v", err.Error()) c.JSON(http.StatusOK, gin.H{ "code": 500, "msg": "Database migration failed", "data": nil, }) c.Abort() return } // create admin user if err = migrate.CreateAdminUser(request.AdminEmail, request.AdminPassword, db); err != nil { logger.Errorf("[Init Mysql] Create admin user failed: %v", err.Error()) c.JSON(http.StatusOK, gin.H{ "code": 500, "msg": "Admin user creation failed", "data": nil, }) c.Abort() return } // write to file if err = os.WriteFile(configPath, fileData, 0644); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "Configuration initialization failed", "data": nil, }) c.Abort() return } c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": "Configuration initialized", "status": true, }) initStatus <- true } func HandleMySQLTest(c *gin.Context) { var request struct { Host string `json:"host"` Port string `json:"port"` Database string `json:"database"` User string `json:"user"` Password string `json:"password"` } if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "msg": "Invalid request", "data": nil, }) c.Abort() return } dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.User, request.Password, request.Host, request.Port, request.Database) var status = true var message string var tx *sql.DB var tables []string db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { logger.Errorf("connect mysql failed, err: %v\n", err.Error()) status = false message = "MySQL connection failed" goto result } tx, _ = db.DB() if err := tx.Ping(); err != nil { logger.Errorf("ping mysql failed, err: %v\n", err.Error()) status = false message = "MySQL connection failed" } tables, err = db.Migrator().GetTables() if err != nil { logger.Errorf("database table check failed, err: %v\n", err.Error()) status = false message = "Database table check failed" goto result } if len(tables) > 0 { status = false message = "The database contains existing data. Please clear it before proceeding with the installation." goto result } result: c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": message, "status": status, }) } func HandleRedisTest(c *gin.Context) { var request struct { Host string `json:"host"` Port string `json:"port"` Password string `json:"password"` } if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "msg": "Invalid request", "data": nil, }) c.Abort() return } if err := tool.RedisPing(fmt.Sprintf("%s:%s", request.Host, request.Port), request.Password, 0); err != nil { c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": nil, "status": false, }) return } c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": nil, "status": true, }) }