diff --git a/Makefile b/Makefile index d4592096e4..5fec938697 100644 --- a/Makefile +++ b/Makefile @@ -148,9 +148,6 @@ artifacts-linux: clean echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env cd ../xgen-v1.0 && pnpm install --no-frozen-lockfile && pnpm run build -# Setup UI - cd ../xgen-v1.0/packages/setup && pnpm install --no-frozen-lockfile && pnpm run build - # Init Application cd ../yao-init && rm -rf .git cd ../yao-init && rm -rf .gitignore @@ -169,7 +166,6 @@ artifacts-linux: clean # ** new repository: https://github.com/YaoApp/dui.git ** mkdir -p .tmp/data/xgen cp -r ./ui .tmp/data/ui - cp -r ../xgen-v1.0/packages/setup/build .tmp/data/xgen/setup cp -r ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0 cp -r ../yao-init .tmp/data/init cp -r yao .tmp/data/ @@ -207,9 +203,6 @@ artifacts-macos: clean echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env cd ../xgen-v1.0 && pnpm install --no-frozen-lockfile && pnpm run build -# Setup UI - cd ../xgen-v1.0/packages/setup && pnpm install --no-frozen-lockfile && pnpm run build - # Init Application cd ../yao-init && rm -rf .git cd ../yao-init && rm -rf .gitignore @@ -228,7 +221,6 @@ artifacts-macos: clean # ** new repository: https://github.com/YaoApp/dui.git ** mkdir -p .tmp/data/xgen cp -r ./ui .tmp/data/ui - cp -r ../xgen-v1.0/packages/setup/build .tmp/data/xgen/setup cp -r ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0 cp -r ../yao-init .tmp/data/init cp -r yao .tmp/data/ @@ -296,10 +288,6 @@ release: clean echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env cd .tmp/xgen/v1.0 && pnpm install --no-frozen-lockfile && pnpm run build -# Setup UI - cd .tmp/xgen/v1.0/packages/setup && pnpm install --no-frozen-lockfile && pnpm run build - - # Checkout init git clone https://github.com/YaoApp/yao-init.git .tmp/yao-init rm -rf .tmp/yao-init/.git @@ -320,7 +308,6 @@ release: clean cp -r ./yao .tmp/data/yao cp -r ./sui/libsui .tmp/data/libsui cp -r .tmp/xgen/v0.9/dist .tmp/data/xgen/v0.9 - cp -r .tmp/xgen/v1.0/packages/setup/build .tmp/data/xgen/setup cp -r .tmp/xgen/v1.0/packages/xgen/dist .tmp/data/xgen/v1.0 cp -r .tmp/yao-init .tmp/data/init go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... diff --git a/cmd/start.go b/cmd/start.go index a81ebc70e3..6cb110c00c 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -26,7 +26,6 @@ import ( "github.com/yaoapp/yao/service" "github.com/yaoapp/yao/setup" "github.com/yaoapp/yao/share" - "github.com/yaoapp/yao/studio" itask "github.com/yaoapp/yao/task" ) @@ -49,17 +48,27 @@ var startCmd = &cobra.Command{ Boot() // Setup - if setup.Check() { - go setup.Start() - select { - case <-setup.Done: - setup.Stop() - Boot() - break - case <-setup.Canceled: + isnew := false + if setup.IsEmptyDir(config.Conf.Root) { + + // In Yao App + if setup.InYaoApp(config.Conf.Root) { + fmt.Println(color.RedString(L("Please run the command in the root directory of project"))) + os.Exit(1) + } + + // Install the init app + if err := install(); err != nil { + fmt.Println(color.RedString(L("Install: %s"), err.Error())) os.Exit(1) - break } + isnew = true + } + + // Is Yao App + if !setup.IsYaoApp(config.Conf.Root) { + fmt.Println(color.RedString(L("yao.app not found"))) + os.Exit(1) } // force debug @@ -103,50 +112,59 @@ var startCmd = &cobra.Command{ fmt.Println(color.WhiteString(L("Root")), color.GreenString(" %s", root)) } + fmt.Println(color.WhiteString(L("Runtime")), color.GreenString(" %s", runtimeMode)) + fmt.Println(color.WhiteString(L("Data")), color.GreenString(" %s", dataRoot)) + fmt.Println(color.WhiteString(L("Listening")), color.GreenString(" %s:%d", config.Conf.Host, config.Conf.Port)) + root, _ := adminRoot() urls := []string{fmt.Sprintf("http://%s:%s", host, port)} if host == "0.0.0.0" { urls, _ = setup.URLs(config.Conf) } - fmt.Println(color.WhiteString(L("Runtime")), color.GreenString(" %s", runtimeMode)) - fmt.Println(color.WhiteString(L("Data")), color.GreenString(" %s", dataRoot)) - fmt.Println(color.WhiteString(L("Listening")), color.GreenString(" %s:%d", config.Conf.Host, config.Conf.Port)) - for _, url := range urls { - fmt.Println(color.CyanString("\n%s", url)) - fmt.Println(color.WhiteString("--------------------------")) - fmt.Println(color.WhiteString(L("Frontend")), color.GreenString(" %s", url)) - fmt.Println(color.WhiteString(L("Dashboard")), color.GreenString(" %s/%s/login/admin", url, strings.Trim(root, "/"))) - fmt.Println(color.WhiteString(L("API")), color.GreenString(" %s/api", url)) - } - // print the messages under the development mode if mode == "development" { // Start Studio Server - go func() { - - err = studio.Load(config.Conf) - if err != nil { - // fmt.Println(color.RedString(L("Studio Load: %s"), err.Error())) - log.Error("Studio Load: %s", err.Error()) - return - } - - err := studio.Start(config.Conf) - if err != nil { - log.Error("Studio Start: %s", err.Error()) - return - } - }() - defer studio.Stop() + // Yao Studio will be deprecated in the future + // go func() { + + // err = studio.Load(config.Conf) + // if err != nil { + // // fmt.Println(color.RedString(L("Studio Load: %s"), err.Error())) + // log.Error("Studio Load: %s", err.Error()) + // return + // } + + // err := studio.Start(config.Conf) + // if err != nil { + // log.Error("Studio Start: %s", err.Error()) + // return + // } + // }() + // defer studio.Stop() printApis(false) printTasks(false) printSchedules(false) printConnectors(false) printStores(false) - printStudio(false, host) + // printStudio(false, host) + + } + + for _, url := range urls { + fmt.Println(color.CyanString("\n%s", url)) + fmt.Println(color.WhiteString("--------------------------")) + fmt.Println(color.WhiteString(L("Website")), color.GreenString(" %s", url)) + fmt.Println(color.WhiteString(L("Admin")), color.GreenString(" %s/%s/login/admin", url, strings.Trim(root, "/"))) + fmt.Println(color.WhiteString(L("API")), color.GreenString(" %s/api", url)) + } + fmt.Println("") + + // Print welcome message for the new application + if isnew { + printWelcome() } // Start Tasks @@ -161,7 +179,7 @@ var startCmd = &cobra.Command{ srv, err := service.Start(config.Conf) defer func() { service.Stop(srv) - fmt.Println(color.GreenString(L("✨EXITED✨"))) + fmt.Println(color.GreenString(L("✨Exited successfully!"))) }() if err != nil { @@ -193,11 +211,12 @@ var startCmd = &cobra.Command{ switch v { case http.READY: - fmt.Println(color.GreenString(L("✨LISTENING✨"))) + fmt.Println(color.GreenString(L("✨Server is up and running..."))) + fmt.Println(color.GreenString("✨Ctrl+C to stop")) break case http.CLOSED: - fmt.Println(color.GreenString(L("✨EXITED✨"))) + fmt.Println(color.GreenString(L("✨Exited successfully!"))) watchDone <- 1 return @@ -218,6 +237,30 @@ var startCmd = &cobra.Command{ }, } +func install() error { + // Copy the app source files from the binary + err := setup.Install(config.Conf.Root) + if err != nil { + return err + } + + // Reload the application engine + Boot() + + // load the application engine + err = engine.Load(config.Conf, engine.LoadOption{Action: "start"}) + if err != nil { + return err + } + + err = setup.Initialize(config.Conf.Root, config.Conf) + if err != nil { + return err + } + + return nil +} + func adminRoot() (string, int) { adminRoot := "/yao/" if share.App.AdminRoot != "" { @@ -229,6 +272,15 @@ func adminRoot() (string, int) { return adminRoot, adminRootLen } +func printWelcome() { + fmt.Println(color.CyanString("\n---------------------------------")) + fmt.Println(color.CyanString(L("🎉 Welcome to Yao 🎉 "))) + fmt.Println(color.CyanString("---------------------------------")) + fmt.Println(color.WhiteString("📚 Documentation:"), color.CyanString("https://yaoapps.com/docs")) + fmt.Println(color.WhiteString("💬 Build App via Chat:"), color.CyanString("https://moapi.ai")) + fmt.Println("") +} + func printConnectors(silent bool) { if len(connector.Connectors) == 0 { @@ -246,8 +298,8 @@ func printConnectors(silent bool) { fmt.Println(color.WhiteString(L("Connectors List (%d)"), len(connector.Connectors))) fmt.Println(color.WhiteString("---------------------------------")) for name := range connector.Connectors { - fmt.Printf(color.CyanString("[Connector]")) - fmt.Printf(color.WhiteString(" %s\t loaded\n", name)) + fmt.Print(color.CyanString("[Connector]")) + fmt.Print(color.WhiteString(" %s\t loaded\n", name)) } } @@ -267,8 +319,8 @@ func printStores(silent bool) { fmt.Println(color.WhiteString(L("Stores List (%d)"), len(connector.Connectors))) fmt.Println(color.WhiteString("---------------------------------")) for name := range store.Pools { - fmt.Printf(color.CyanString("[Store]")) - fmt.Printf(color.WhiteString(" %s\t loaded\n", name)) + fmt.Print(color.CyanString("[Store]")) + fmt.Print(color.WhiteString(" %s\t loaded\n", name)) } } @@ -285,13 +337,13 @@ func printStudio(silent bool, host string) { fmt.Println(color.WhiteString("\n---------------------------------")) fmt.Println(color.WhiteString(L("Yao Studio Server"))) fmt.Println(color.WhiteString("---------------------------------")) - fmt.Printf(color.CyanString("HOST : ")) - fmt.Printf(color.WhiteString(" %s\n", config.Conf.Host)) - fmt.Printf(color.CyanString("PORT : ")) - fmt.Printf(color.WhiteString(" %d\n", config.Conf.Studio.Port)) + fmt.Print(color.CyanString("HOST : ")) + fmt.Print(color.WhiteString(" %s\n", config.Conf.Host)) + fmt.Print(color.CyanString("PORT : ")) + fmt.Print(color.WhiteString(" %d\n", config.Conf.Studio.Port)) if config.Conf.Studio.Auto { - fmt.Printf(color.CyanString("SECRET: ")) - fmt.Printf(color.WhiteString(" %s\n", config.Conf.Studio.Secret)) + fmt.Print(color.CyanString("SECRET: ")) + fmt.Print(color.WhiteString(" %s\n", config.Conf.Studio.Secret)) } } @@ -320,8 +372,8 @@ func printSchedules(silent bool) { if sch.TaskName != "" { process = fmt.Sprintf("Task: %s", sch.TaskName) } - fmt.Printf(color.CyanString("[Schedule] %s %s", sch.Schedule, name)) - fmt.Printf(color.WhiteString("\t%s\t%s\n", sch.Name, process)) + fmt.Print(color.CyanString("[Schedule] %s %s", sch.Schedule, name)) + fmt.Print(color.WhiteString("\t%s\t%s\n", sch.Name, process)) } } @@ -342,8 +394,8 @@ func printTasks(silent bool) { fmt.Println(color.WhiteString(L("Tasks List (%d)"), len(task.Tasks))) fmt.Println(color.WhiteString("---------------------------------")) for _, t := range task.Tasks { - fmt.Printf(color.CyanString("[Task] %s", t.Option.Name)) - fmt.Printf(color.WhiteString("\t workers: %d\n", t.Option.WorkerNums)) + fmt.Print(color.CyanString("[Task] %s", t.Option.Name)) + fmt.Print(color.WhiteString("\t workers: %d\n", t.Option.WorkerNums)) } } diff --git a/config/config.go b/config/config.go index 7f61a7b9b3..105784b856 100644 --- a/config/config.go +++ b/config/config.go @@ -150,17 +150,19 @@ func OpenLog() { logfile, err := filepath.Abs(Conf.Log) if err != nil { - log.With(log.F{"file": logfile}).Error(err.Error()) return } logpath := filepath.Dir(logfile) - if _, err := os.Stat(logpath); os.IsNotExist(err) { - if err := os.MkdirAll(logpath, os.ModePerm); err != nil { - log.With(log.F{"file": logfile}).Error(err.Error()) - return - } + + // Check if the log path exists + if _, err := os.Stat(logpath); errors.Is(err, os.ErrNotExist) { + LogOutput, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0666) + log.SetOutput(LogOutput) + gin.DefaultWriter = io.MultiWriter(LogOutput) + return } + LogOutput = &lumberjack.Logger{ Filename: logfile, MaxSize: Conf.LogMaxSize, // megabytes diff --git a/setup/check.go b/setup/check.go index e6bfa131d7..fe59160e3c 100644 --- a/setup/check.go +++ b/setup/check.go @@ -2,114 +2,52 @@ package setup import ( "fmt" - "net" "os" "path/filepath" - "strings" - "time" - "github.com/yaoapp/xun/capsule" "github.com/yaoapp/yao/config" ) -// Check if the app is installed -// true: start setup, false: start app -func Check() bool { - - // check app source - if !appSourceExists() { - return true - } - - // check env file - root := appRoot() - envfile := filepath.Join(root, ".env") - if _, err := os.Stat(envfile); err != nil && os.IsNotExist(err) { - return true +// InYaoApp Check if the current directory is a yao app +func InYaoApp(root string) bool { + // Check current directory and parent directories + for root != "/" { + if IsYaoApp(root) { + return true + } + root = filepath.Dir(root) } - return false } -func appSourceExists() bool { - - appsource := appSource() - if strings.HasSuffix(appsource, ".yaz") || strings.HasPrefix(appsource, "::binary") { - return true - } - - // check app.yao/app.json/app.jsonc - root := appRoot() +// IsYaoApp Check if the directory is a yao app +func IsYaoApp(root string) bool { appfiles := []string{"app.yao", "app.json", "app.jsonc"} - exist := false + yaoapp := false for _, appfile := range appfiles { appfile = filepath.Join(root, appfile) if _, err := os.Stat(appfile); err == nil { - exist = true + yaoapp = true break } } - - return exist -} - -// ValidateHosting host ports -func ValidateHosting(option map[string]string) error { - if option["YAO_PORT"] == "" { - return fmt.Errorf("监听端口必须填写") - } - - if option["YAO_STUDIO_PORT"] == option["YAO_PORT"] { - return fmt.Errorf("监听端口和 Studio 端口不能相同") - } - - if option["YAO_PORT"] != SetupPort { - conn, _ := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", option["YAO_PORT"]), time.Second) - if conn != nil { - defer conn.Close() - return fmt.Errorf("监听端口 %s 已被占用", option["YAO_PORT"]) - } - } - - if option["YAO_STUDIO_PORT"] != SetupPort { - conn, _ := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", option["YAO_STUDIO_PORT"]), time.Second) - if conn != nil { - defer conn.Close() - return fmt.Errorf("Studio 端口 %s 已被占用", option["YAO_STUDIO_PORT"]) - } - } - - return nil + return yaoapp } -// ValidateDB db connection -func ValidateDB(option map[string]string) error { - - driver, dsn, err := getDSN(option) +// IsEmptyDir Check if the directory is empty +func IsEmptyDir(dir string) bool { + f, err := os.Open(dir) if err != nil { - return fmt.Errorf("连接失败 %s", err.Error()) - } - - m, err := capsule.Add("validate", driver, dsn) - if err != nil { - return fmt.Errorf("连接失败 %s", err.Error()) - } - - conn, err := m.Primary() - if err != nil { - return fmt.Errorf("连接失败 %s", err.Error()) + fmt.Println("Can't open the directory: ", err) + return true } + defer f.Close() - err = conn.Ping(2 * time.Second) + files, err := f.Readdir(0) if err != nil { - return fmt.Errorf("连接失败 %s", err.Error()) + return true } - - return nil -} - -func appSource() string { - return os.Getenv("YAO_APP_SOURCE") + return len(files) == 0 } func appRoot() string { @@ -118,16 +56,14 @@ func appRoot() string { if root == "" { path, err := os.Getwd() if err != nil { - printError("无法获取应用目录: %s", err) - Stop() + printError("Can't get the application directory: %s", err) } root = path } root, err := filepath.Abs(root) if err != nil { - printError("无法获取应用目录: %s", err) - Stop() + printError("Can't get the application directory: %s", err) } return root @@ -139,35 +75,3 @@ func getConfig() (config.Config, error) { cfg := config.LoadFrom(envfile) return cfg, nil } - -func hasInstalled(cfg config.Config) bool { - - return false - - // switch cfg.DB.Driver { - - // case "sqlite3": - // if cfg.DB.Primary != nil && len(cfg.DB.Primary) > 0 { - - // dbfile, err := filepath.Abs(cfg.DB.Primary[0]) - // if err != nil { - // return false - // } - - // if _, err := os.Stat(dbfile); err != nil && os.IsNotExist(err) { - // return false - // } - - // return false - // } - // break - - // case "mysql": - // if cfg.DB.Primary != nil && len(cfg.DB.Primary) > 0 { - // return true - // } - // break - // } - - // return false -} diff --git a/setup/env.go b/setup/env.go deleted file mode 100644 index aeb1755835..0000000000 --- a/setup/env.go +++ /dev/null @@ -1,65 +0,0 @@ -package setup - -import ( - "os" - "path/filepath" - - "github.com/joho/godotenv" -) - -func envSet(file, name, value string) error { - - file, err := filepath.Abs(file) - if err != nil { - dir := filepath.Dir(file) - err := os.MkdirAll(dir, os.ModePerm) - if err != nil && !os.IsExist(err) { - return err - } - - } - - if _, err := os.Stat(file); err != nil && os.IsNotExist(err) { - err = os.WriteFile(file, []byte{}, 0644) - if err != nil { - return err - } - - err = godotenv.Write(map[string]string{"YAO_ENV": "development"}, file) - if err != nil { - return err - } - } - - vars, err := godotenv.Read(file) - if err != nil { - return err - } - - vars[name] = value - return godotenv.Write(vars, file) -} - -func envGet(file, name string) (string, error) { - - v := os.Getenv(name) - if v != "" { - return v, nil - } - - vars, err := godotenv.Read(file) - if err == nil { - return "", err - } - return vars[name], nil -} - -func envHas(file, name string) (bool, error) { - - v, err := envGet(file, name) - if err != nil { - return false, nil - } - - return v != "", nil -} diff --git a/setup/handler.go b/setup/handler.go deleted file mode 100644 index 27e0144269..0000000000 --- a/setup/handler.go +++ /dev/null @@ -1,265 +0,0 @@ -package setup - -import ( - "fmt" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/gin-gonic/gin" - "github.com/yaoapp/kun/exception" - "github.com/yaoapp/xun" - "github.com/yaoapp/yao/engine" - "github.com/yaoapp/yao/widgets/app" -) - -// recovered custom recovered -func recovered(c *gin.Context, recovered interface{}) { - - var code = http.StatusInternalServerError - - if err, ok := recovered.(string); ok { - c.JSON(code, xun.R{ - "code": code, - "message": fmt.Sprintf("%s", err), - }) - } else if err, ok := recovered.(exception.Exception); ok { - code = err.Code - c.JSON(code, xun.R{ - "code": code, - "message": err.Message, - }) - } else if err, ok := recovered.(*exception.Exception); ok { - code = err.Code - c.JSON(code, xun.R{ - "code": code, - "message": err.Message, - }) - } else { - c.JSON(code, xun.R{ - "code": code, - "message": fmt.Sprintf("%v", recovered), - }) - } - - c.AbortWithStatus(code) -} - -// { -// "env": { -// "YAO_LANG": "中文", -// "YAO_ENV": "开发模式(推荐)", -// "YAO_PORT": "5099", -// "YAO_STUDIO_PORT": "5077" -// }, -// "db": { -// "type": "sqlite", -// "option.file": "db/yao.db" -// } -// } -func runSetup(c *gin.Context) { - - payload := getSetting(c) - - cfg, err := getConfig() - if err != nil { - c.JSON(500, gin.H{"code": 500, "message": err.Error()}) - return - } - - if !Check() { - c.JSON(403, gin.H{"code": 400, "message": "应用已安装, 删除 .env 文件和 db 目录后重试"}) - return - } - - err = Install(payload) - if err != nil { - c.JSON(500, gin.H{"code": 500, "message": err.Error()}) - return - } - - // Reload Config - cfg, err = getConfig() - if err != nil { - c.JSON(500, gin.H{"code": 500, "message": err.Error()}) - return - } - - err = engine.Load(cfg, engine.LoadOption{}) - if err != nil { - c.JSON(500, gin.H{"code": 500, "message": err.Error()}) - return - } - - // Return - urls, err := AdminURL(cfg) - if err != nil { - c.JSON(500, gin.H{"code": 500, "message": err.Error()}) - return - } - - adminRoot := "yao" - if app.Setting.AdminRoot != "" { - adminRoot = app.Setting.AdminRoot - } - adminRoot = strings.Trim(adminRoot, "/") - - c.JSON(200, gin.H{ - "code": 200, - "message": "安装成功", - "urls": urls, - "port": cfg.Port, - "root": adminRoot, - }) - - Complete() -} - -func runCheck(c *gin.Context) { - - payload := getCheck(c) - dbOption, err := getDBOption(map[string]map[string]string{"db": payload}) - if err != nil { - c.JSON(500, gin.H{"code": 500, "message": err.Error()}) - return - } - - err = ValidateDB(dbOption) - if err != nil { - c.JSON(500, gin.H{"code": 500, "message": err.Error()}) - return - } - - c.JSON(200, gin.H{"code": 200}) -} - -func getCheck(c *gin.Context) map[string]string { - var payload map[string]string - err := c.ShouldBindJSON(&payload) - if err != nil { - c.JSON(500, gin.H{"code": 400, "message": err.Error()}) - c.Abort() - return nil - } - return payload -} - -func getSetting(c *gin.Context) map[string]map[string]string { - var payload map[string]map[string]string - err := c.ShouldBindJSON(&payload) - if err != nil { - c.JSON(500, gin.H{"code": 400, "message": err.Error()}) - c.Abort() - return nil - } - return payload -} - -func getENVOption(payload map[string]map[string]string) (map[string]string, error) { - env, has := payload["env"] - if !has { - return nil, fmt.Errorf("缺少服务配置信息") - } - - if env["YAO_ENV"] == "开发模式(推荐)" { - env["YAO_ENV"] = "development" - } else { - env["YAO_ENV"] = "production" - } - - if env["YAO_LANG"] == "中文" { - env["YAO_LANG"] = "zh-cn" - } else { - env["YAO_LANG"] = "en-us" - } - return env, nil -} - -func getDBOption(payload map[string]map[string]string) (map[string]string, error) { - - db, has := payload["db"] - if !has { - return nil, fmt.Errorf("缺少数据库配置信息") - } - - dbOption := map[string]string{} - switch db["type"] { - case "", "sqlite", "sqlite3": - dbOption["type"] = "sqlite3" - dbOption["file"] = db["option.file"] - return dbOption, nil - - case "mysql": - dbOption["type"] = "mysql" - dbOption["db"] = db["option.db"] - dbOption["host"] = db["option.host.host"] - dbOption["port"] = db["option.host.port"] - dbOption["user"] = db["option.host.user"] - dbOption["pass"] = db["option.host.pass"] - return dbOption, nil - } - - return nil, fmt.Errorf("数据库驱动暂不支持") -} - -func getDSN(dbOption map[string]string) (string, string, error) { - - switch dbOption["type"] { - case "", "sqlite", "sqlite3": - root := appRoot() - var err error - db := filepath.Join("db", "yao.db") - if v, has := dbOption["file"]; has { - db = v - } - - if !strings.HasPrefix(db, "/") { - db = filepath.Join(root, db) - db, err = filepath.Abs(db) - if err != nil && !os.IsNotExist(err) { - return "", "", err - } - } - - dir := filepath.Dir(db) - err = os.MkdirAll(dir, os.ModePerm) - if err != nil && !os.IsExist(err) { - return "", "", err - } - - return "sqlite3", db, nil - - case "mysql": - - db := "yao" - if v, has := dbOption["db"]; has { - db = v - } - - host := "127.0.0.1" - if v, has := dbOption["host"]; has { - host = v - } - - port := "3306" - if v, has := dbOption["port"]; has { - port = v - } - - user := "root" - if v, has := dbOption["user"]; has { - user = v - } - - pass := "" - if v, has := dbOption["pass"]; has { - pass = v - } - - return "mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, pass, host, port, db), nil - } - - return "", "", fmt.Errorf("driver does not support") - -} diff --git a/setup/install.go b/setup/install.go index ad7c0682ea..c45a0fe379 100644 --- a/setup/install.go +++ b/setup/install.go @@ -1,140 +1,23 @@ package setup import ( - "fmt" "os" "path/filepath" "strings" - "time" - "github.com/google/uuid" "github.com/yaoapp/gou/model" "github.com/yaoapp/gou/process" - v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/data" - "github.com/yaoapp/yao/engine" - "github.com/yaoapp/yao/studio" "github.com/yaoapp/yao/widgets/app" ) -// Install app install -// -// { -// "env": { -// "YAO_LANG": "中文", -// "YAO_ENV": "开发模式(推荐)", -// "YAO_PORT": "5099", -// "YAO_STUDIO_PORT": "5077" -// }, -// "db": { -// "type": "sqlite", -// "option.file": "db/yao.db" -// } -// } -// -// { -// "env": { -// "YAO_LANG": "中文", -// "YAO_ENV": "开发模式(推荐)", -// "YAO_PORT": "5099", -// "YAO_STUDIO_PORT": "5077" -// }, -// "db": { -// "type": "mysql", -// "option.db": "yao", -// "option.host.host": "127.0.0.1", -// "option.host.port": "3306", -// "option.host.user": "root", -// "option.host.pass": "123456" -// } -// } -func Install(payload map[string]map[string]string) error { +// Install the app to the root directory +func Install(root string) error { - dbOption, err := getDBOption(payload) - if err != nil { - return err - } - - err = ValidateDB(dbOption) - if err != nil { - return err - } - - envOption, err := getENVOption(payload) - if err != nil { - return err - } - - err = ValidateHosting(envOption) - if err != nil { - return err - } - - root := appRoot() - err = makeService(root, "0.0.0.0", envOption["YAO_PORT"], envOption["YAO_STUDIO_PORT"], envOption["YAO_LANG"]) - if err != nil { - return err - } - - err = makeDB(root, dbOption) - if err != nil { - return err - } - - err = makeSession(root) - if err != nil { - return err - } - - err = makeLog(root) - if err != nil { - return err - } - - err = makeDirs(root) - if err != nil { - return err - } - - err = makeInit(root) - if err != nil { - return err - } - - cfg, err := getConfig() - if err != nil { - return err - } - - // Load engine - err = engine.Load(cfg, engine.LoadOption{ - Action: "install", - }) - - if err != nil { - return err - } - - defer func() { - engine.Unload() - time.Sleep(time.Millisecond * 200) - }() - - // Load Studio - err = studio.Load(cfg) - if err != nil { - return err - } - - // Migrage & Setup - err = makeMigrate(root, cfg) - if err != nil { - return err - } - - err = makeSetup(root, cfg) + // Copy the init source files + err := makeInit(root) if err != nil { return err } @@ -142,119 +25,27 @@ func Install(payload map[string]map[string]string) error { return nil } -func makeService(root string, host string, port string, studioPort string, lang string) error { - file := filepath.Join(root, ".env") - err := envSet(file, "YAO_HOST", host) - if err != nil { - return err - } +// Initialize the installed app +func Initialize(root string, cfg config.Config) error { - err = envSet(file, "YAO_PORT", port) + // Migration + err := makeMigrate() if err != nil { return err } - err = envSet(file, "YAO_STUDIO_PORT", studioPort) + // Execute the setup hook + err = makeSetup(cfg) if err != nil { return err } - return envSet(file, "YAO_LANG", lang) -} - -func makeDB(root string, option map[string]string) error { - - driver, dsn, err := getDSN(option) - if driver != "mysql" && driver != "sqlite3" { - return fmt.Errorf("数据库驱动应该为: mysql/sqlite3") - } - - file := filepath.Join(root, ".env") - if err != nil { - return err - } - - err = envSet(file, "YAO_DB_DRIVER", driver) - if err != nil { - return err - } - - return envSet(file, "YAO_DB_PRIMARY", dsn) -} - -func makeSession(root string) error { - file := filepath.Join(root, ".env") - if has, _ := envHas(file, "YAO_SESSION_STORE"); has { - return nil - } - - if has, _ := envHas(file, "YAO_SESSION_FILE"); has { - return nil - } - - err := os.MkdirAll(filepath.Join(root, "data"), os.ModePerm) - if err != nil && !os.IsExist(err) { - return err - } - - err = envSet(file, "YAO_SESSION_STORE", "file") - if err != nil { - return err - } - - ssfile := filepath.Join(root, "db", ".session") - return envSet(file, "YAO_SESSION_FILE", ssfile) -} - -func makeLog(root string) error { - file := filepath.Join(root, ".env") - if has, _ := envHas(file, "YAO_LOG_MODE"); has { - return nil - } - - if has, _ := envHas(file, "YAO_LOG"); has { - return nil - } - - err := os.MkdirAll(filepath.Join(root, "data"), os.ModePerm) - if err != nil && !os.IsExist(err) { - return err - } - - err = envSet(file, "YAO_LOG_MODE", "TEXT") - if err != nil { - return err - } - - logfile := filepath.Join(root, "logs", "application.log") - return envSet(file, "YAO_LOG", logfile) -} - -func makeDirs(root string) error { - - if !appSourceExists() { - err := os.MkdirAll(filepath.Join(root, "public"), os.ModePerm) - if err != nil && !os.IsExist(err) { - return err - } - } - - err := os.MkdirAll(filepath.Join(root, "logs"), os.ModePerm) - if err != nil && !os.IsExist(err) { - return err - } - - err = os.MkdirAll(filepath.Join(root, "db"), os.ModePerm) - if err != nil && !os.IsExist(err) { - return err - } - return nil } func makeInit(root string) error { - if appSourceExists() { + if !IsEmptyDir(root) { return nil } @@ -285,7 +76,7 @@ func makeInit(root string) error { return nil } -func makeMigrate(root string, cfg config.Config) error { +func makeMigrate() error { // Do Stuff Here for _, mod := range model.Models { @@ -308,35 +99,10 @@ func makeMigrate(root string, cfg config.Config) error { return nil } -func makeSetup(root string, cfg config.Config) error { +func makeSetup(cfg config.Config) error { if app.Setting != nil && app.Setting.Setup != "" { - if strings.HasPrefix(app.Setting.Setup, "studio.") { - names := strings.Split(app.Setting.Setup, ".") - if len(names) < 3 { - return fmt.Errorf("setup studio script %s error", app.Setting.Setup) - } - - service := strings.Join(names[1:len(names)-1], ".") - method := names[len(names)-1] - - script, err := v8.SelectRoot(service) - if err != nil { - return err - } - - sid := uuid.NewString() - ctx, err := script.NewContext(fmt.Sprintf("%v", sid), nil) - if err != nil { - return err - } - defer ctx.Close() - - _, err = ctx.Call(method) - return err - } - p, err := process.Of(app.Setting.Setup, cfg) if err != nil { return err diff --git a/setup/setup.go b/setup/setup.go index ee9470365f..b55f173f61 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -3,164 +3,12 @@ package setup import ( "fmt" "net" - "net/http" "os" - "os/signal" - "path/filepath" - "strings" - "syscall" "github.com/fatih/color" - "github.com/gin-gonic/gin" "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/data" - "github.com/yaoapp/yao/engine" - "github.com/yaoapp/yao/share" - "github.com/yaoapp/yao/widgets/app" ) -// SetupPort setup port -var SetupPort string = "5099" - -// XGenSetupServer XGen Setup -var XGenSetupServer http.Handler = http.FileServer(data.Setup()) - -// Done check done -var Done = make(chan bool, 1) - -// shutdown check done -var shutdown = make(chan bool, 1) - -// Canceled check if setup is canceld -var Canceled = make(chan bool, 1) - -// Start start the studio api server -func Start() (err error) { - - // recive interrupt signal - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) - - errCh := make(chan error, 1) - - // Set router - gin.SetMode(gin.ReleaseMode) - router := gin.New() - - if os.Getenv("YAO_SETUP_DEV") != "" { - fmt.Println(color.WhiteString("\nSETUP DEV: %s\n", os.Getenv("YAO_SETUP_DEV"))) - } - - router.Use(func(c *gin.Context) { - length := len(c.Request.URL.Path) - if length >= 5 && c.Request.URL.Path[0:5] == "/api/" { - c.Next() - return - } - - if os.Getenv("YAO_SETUP_DEV") != "" { - root, err := filepath.Abs(os.Getenv("YAO_SETUP_DEV")) - if err != nil { - printError("%s", err) - } - - static := http.FileServer(http.Dir(root)) - static.ServeHTTP(c.Writer, c.Request) - return - } - - // Setup Pages - XGenSetupServer.ServeHTTP(c.Writer, c.Request) - return - }, gin.CustomRecovery(recovered)) - - router.POST("/api/__yao/app/check", runCheck) - router.POST("/api/__yao/app/setup", runSetup) - - // Server setting - addr := fmt.Sprintf(":%s", SetupPort) - - // Listen - l, err := net.Listen("tcp4", addr) - if err != nil { - addr = ":0" - l, err = net.Listen("tcp4", addr) - if err != nil { - return err - } - } - - srv := &http.Server{ - Addr: addr, - Handler: router, - } - defer func() { - // printInfo("[Setup] %s Close Serve", addr) - err = srv.Close() - if err != nil { - printError("[Setup] Error (%v)", err) - } - }() - - // start serve - go func() { - // printInfo("[Setup] Starting: %s", addr) - if err := srv.Serve(l); err != nil && err != http.ErrServerClosed { - errCh <- err - } - }() - - welcome(l) - - select { - - case <-shutdown: - printInfo("Setup Shutdown") - return err - - case <-interrupt: - printInfo("Setup Interrupt") - Canceled <- true - return err - - case err := <-errCh: - printError("Setup Error: ", err) - Canceled <- true - return err - } -} - -// Complete stop the studio api server -func Complete() { - engine.Unload() - Done <- true -} - -// Stop stop the studio api server -func Stop() { - shutdown <- true -} - -// AdminURL get admin url -func AdminURL(cfg config.Config) ([]string, error) { - - urls, err := URLs(cfg) - if err != nil { - return nil, err - } - - adminRoot := "yao" - if app.Setting.AdminRoot != "" { - adminRoot = app.Setting.AdminRoot - } - adminRoot = strings.Trim(adminRoot, "/") - - for i := range urls { - urls[i] = fmt.Sprintf("%s/%s/", urls[i], adminRoot) - } - return urls, nil -} - // URLs get admin url func URLs(cfg config.Config) ([]string, error) { @@ -176,31 +24,6 @@ func URLs(cfg config.Config) ([]string, error) { return ips, nil } -func welcome(l net.Listener) { - fmt.Println(color.WhiteString("---------------------------------")) - fmt.Println(color.WhiteString("Yao Application Setup v%s", share.VERSION)) - fmt.Println(color.WhiteString("---------------------------------")) - - ips, err := Ips() - if err != nil { - printError("Error: ", err.Error()) - } - - addr := strings.Split(l.Addr().String(), ":") - if len(addr) != 2 { - printError("Error: can't get port") - } - - port := addr[1] - fmt.Println(color.WhiteString("\nOpen URL in the browser to continue:\n")) - for _, ip := range ips { - printInfo("http://%s:%s", ip, port) - } - - fmt.Println() - SetupPort = port -} - func printError(message string, args ...interface{}) { fmt.Println(color.RedString(message, args...)) os.Exit(1)