Merge pull request #796 from trheyi/main

Enhance yao start output
This commit is contained in:
Max 2024-11-29 18:44:41 +08:00 committed by GitHub
commit 675a0c40d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 151 additions and 947 deletions

View file

@ -148,9 +148,6 @@ artifacts-linux: clean
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
cd ../xgen-v1.0 && pnpm install --no-frozen-lockfile && pnpm run build 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 # Init Application
cd ../yao-init && rm -rf .git cd ../yao-init && rm -rf .git
cd ../yao-init && rm -rf .gitignore cd ../yao-init && rm -rf .gitignore
@ -169,7 +166,6 @@ artifacts-linux: clean
# ** new repository: https://github.com/YaoApp/dui.git ** # ** new repository: https://github.com/YaoApp/dui.git **
mkdir -p .tmp/data/xgen mkdir -p .tmp/data/xgen
cp -r ./ui .tmp/data/ui 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 ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
cp -r ../yao-init .tmp/data/init cp -r ../yao-init .tmp/data/init
cp -r yao .tmp/data/ cp -r yao .tmp/data/
@ -207,9 +203,6 @@ artifacts-macos: clean
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
cd ../xgen-v1.0 && pnpm install --no-frozen-lockfile && pnpm run build 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 # Init Application
cd ../yao-init && rm -rf .git cd ../yao-init && rm -rf .git
cd ../yao-init && rm -rf .gitignore cd ../yao-init && rm -rf .gitignore
@ -228,7 +221,6 @@ artifacts-macos: clean
# ** new repository: https://github.com/YaoApp/dui.git ** # ** new repository: https://github.com/YaoApp/dui.git **
mkdir -p .tmp/data/xgen mkdir -p .tmp/data/xgen
cp -r ./ui .tmp/data/ui 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 ../xgen-v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
cp -r ../yao-init .tmp/data/init cp -r ../yao-init .tmp/data/init
cp -r yao .tmp/data/ cp -r yao .tmp/data/
@ -296,10 +288,6 @@ release: clean
echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env 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 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 # Checkout init
git clone https://github.com/YaoApp/yao-init.git .tmp/yao-init git clone https://github.com/YaoApp/yao-init.git .tmp/yao-init
rm -rf .tmp/yao-init/.git rm -rf .tmp/yao-init/.git
@ -320,7 +308,6 @@ release: clean
cp -r ./yao .tmp/data/yao cp -r ./yao .tmp/data/yao
cp -r ./sui/libsui .tmp/data/libsui cp -r ./sui/libsui .tmp/data/libsui
cp -r .tmp/xgen/v0.9/dist .tmp/data/xgen/v0.9 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/xgen/v1.0/packages/xgen/dist .tmp/data/xgen/v1.0
cp -r .tmp/yao-init .tmp/data/init cp -r .tmp/yao-init .tmp/data/init
go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/...

View file

@ -26,7 +26,6 @@ import (
"github.com/yaoapp/yao/service" "github.com/yaoapp/yao/service"
"github.com/yaoapp/yao/setup" "github.com/yaoapp/yao/setup"
"github.com/yaoapp/yao/share" "github.com/yaoapp/yao/share"
"github.com/yaoapp/yao/studio"
itask "github.com/yaoapp/yao/task" itask "github.com/yaoapp/yao/task"
) )
@ -49,17 +48,27 @@ var startCmd = &cobra.Command{
Boot() Boot()
// Setup // Setup
if setup.Check() { isnew := false
go setup.Start() if setup.IsEmptyDir(config.Conf.Root) {
select {
case <-setup.Done: // In Yao App
setup.Stop() if setup.InYaoApp(config.Conf.Root) {
Boot() fmt.Println(color.RedString(L("Please run the command in the root directory of project")))
break
case <-setup.Canceled:
os.Exit(1) os.Exit(1)
break
} }
// Install the init app
if err := install(); err != nil {
fmt.Println(color.RedString(L("Install: %s"), err.Error()))
os.Exit(1)
}
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 // 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("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() root, _ := adminRoot()
urls := []string{fmt.Sprintf("http://%s:%s", host, port)} urls := []string{fmt.Sprintf("http://%s:%s", host, port)}
if host == "0.0.0.0" { if host == "0.0.0.0" {
urls, _ = setup.URLs(config.Conf) 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 // print the messages under the development mode
if mode == "development" { if mode == "development" {
// Start Studio Server // Start Studio Server
go func() { // Yao Studio will be deprecated in the future
// go func() {
err = studio.Load(config.Conf) // err = studio.Load(config.Conf)
if err != nil { // if err != nil {
// fmt.Println(color.RedString(L("Studio Load: %s"), err.Error())) // // fmt.Println(color.RedString(L("Studio Load: %s"), err.Error()))
log.Error("Studio Load: %s", err.Error()) // log.Error("Studio Load: %s", err.Error())
return // return
} // }
err := studio.Start(config.Conf) // err := studio.Start(config.Conf)
if err != nil { // if err != nil {
log.Error("Studio Start: %s", err.Error()) // log.Error("Studio Start: %s", err.Error())
return // return
} // }
}() // }()
defer studio.Stop() // defer studio.Stop()
printApis(false) printApis(false)
printTasks(false) printTasks(false)
printSchedules(false) printSchedules(false)
printConnectors(false) printConnectors(false)
printStores(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 // Start Tasks
@ -161,7 +179,7 @@ var startCmd = &cobra.Command{
srv, err := service.Start(config.Conf) srv, err := service.Start(config.Conf)
defer func() { defer func() {
service.Stop(srv) service.Stop(srv)
fmt.Println(color.GreenString(L("✨EXITED✨"))) fmt.Println(color.GreenString(L("✨Exited successfully!")))
}() }()
if err != nil { if err != nil {
@ -193,11 +211,12 @@ var startCmd = &cobra.Command{
switch v { switch v {
case http.READY: 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 break
case http.CLOSED: case http.CLOSED:
fmt.Println(color.GreenString(L("✨EXITED✨"))) fmt.Println(color.GreenString(L("✨Exited successfully!")))
watchDone <- 1 watchDone <- 1
return 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) { func adminRoot() (string, int) {
adminRoot := "/yao/" adminRoot := "/yao/"
if share.App.AdminRoot != "" { if share.App.AdminRoot != "" {
@ -229,6 +272,15 @@ func adminRoot() (string, int) {
return adminRoot, adminRootLen 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) { func printConnectors(silent bool) {
if len(connector.Connectors) == 0 { 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(L("Connectors List (%d)"), len(connector.Connectors)))
fmt.Println(color.WhiteString("---------------------------------")) fmt.Println(color.WhiteString("---------------------------------"))
for name := range connector.Connectors { for name := range connector.Connectors {
fmt.Printf(color.CyanString("[Connector]")) fmt.Print(color.CyanString("[Connector]"))
fmt.Printf(color.WhiteString(" %s\t loaded\n", name)) 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(L("Stores List (%d)"), len(connector.Connectors)))
fmt.Println(color.WhiteString("---------------------------------")) fmt.Println(color.WhiteString("---------------------------------"))
for name := range store.Pools { for name := range store.Pools {
fmt.Printf(color.CyanString("[Store]")) fmt.Print(color.CyanString("[Store]"))
fmt.Printf(color.WhiteString(" %s\t loaded\n", name)) 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("\n---------------------------------"))
fmt.Println(color.WhiteString(L("Yao Studio Server"))) fmt.Println(color.WhiteString(L("Yao Studio Server")))
fmt.Println(color.WhiteString("---------------------------------")) fmt.Println(color.WhiteString("---------------------------------"))
fmt.Printf(color.CyanString("HOST : ")) fmt.Print(color.CyanString("HOST : "))
fmt.Printf(color.WhiteString(" %s\n", config.Conf.Host)) fmt.Print(color.WhiteString(" %s\n", config.Conf.Host))
fmt.Printf(color.CyanString("PORT : ")) fmt.Print(color.CyanString("PORT : "))
fmt.Printf(color.WhiteString(" %d\n", config.Conf.Studio.Port)) fmt.Print(color.WhiteString(" %d\n", config.Conf.Studio.Port))
if config.Conf.Studio.Auto { if config.Conf.Studio.Auto {
fmt.Printf(color.CyanString("SECRET: ")) fmt.Print(color.CyanString("SECRET: "))
fmt.Printf(color.WhiteString(" %s\n", config.Conf.Studio.Secret)) fmt.Print(color.WhiteString(" %s\n", config.Conf.Studio.Secret))
} }
} }
@ -320,8 +372,8 @@ func printSchedules(silent bool) {
if sch.TaskName != "" { if sch.TaskName != "" {
process = fmt.Sprintf("Task: %s", sch.TaskName) process = fmt.Sprintf("Task: %s", sch.TaskName)
} }
fmt.Printf(color.CyanString("[Schedule] %s %s", sch.Schedule, name)) fmt.Print(color.CyanString("[Schedule] %s %s", sch.Schedule, name))
fmt.Printf(color.WhiteString("\t%s\t%s\n", sch.Name, process)) 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(L("Tasks List (%d)"), len(task.Tasks)))
fmt.Println(color.WhiteString("---------------------------------")) fmt.Println(color.WhiteString("---------------------------------"))
for _, t := range task.Tasks { for _, t := range task.Tasks {
fmt.Printf(color.CyanString("[Task] %s", t.Option.Name)) fmt.Print(color.CyanString("[Task] %s", t.Option.Name))
fmt.Printf(color.WhiteString("\t workers: %d\n", t.Option.WorkerNums)) fmt.Print(color.WhiteString("\t workers: %d\n", t.Option.WorkerNums))
} }
} }

View file

@ -150,17 +150,19 @@ func OpenLog() {
logfile, err := filepath.Abs(Conf.Log) logfile, err := filepath.Abs(Conf.Log)
if err != nil { if err != nil {
log.With(log.F{"file": logfile}).Error(err.Error())
return return
} }
logpath := filepath.Dir(logfile) logpath := filepath.Dir(logfile)
if _, err := os.Stat(logpath); os.IsNotExist(err) {
if err := os.MkdirAll(logpath, os.ModePerm); err != nil { // Check if the log path exists
log.With(log.F{"file": logfile}).Error(err.Error()) if _, err := os.Stat(logpath); errors.Is(err, os.ErrNotExist) {
return LogOutput, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0666)
} log.SetOutput(LogOutput)
gin.DefaultWriter = io.MultiWriter(LogOutput)
return
} }
LogOutput = &lumberjack.Logger{ LogOutput = &lumberjack.Logger{
Filename: logfile, Filename: logfile,
MaxSize: Conf.LogMaxSize, // megabytes MaxSize: Conf.LogMaxSize, // megabytes

View file

@ -2,114 +2,52 @@ package setup
import ( import (
"fmt" "fmt"
"net"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time"
"github.com/yaoapp/xun/capsule"
"github.com/yaoapp/yao/config" "github.com/yaoapp/yao/config"
) )
// Check if the app is installed // InYaoApp Check if the current directory is a yao app
// true: start setup, false: start app func InYaoApp(root string) bool {
func Check() bool { // Check current directory and parent directories
for root != "/" {
// check app source if IsYaoApp(root) {
if !appSourceExists() { return true
return true }
root = filepath.Dir(root)
} }
// check env file
root := appRoot()
envfile := filepath.Join(root, ".env")
if _, err := os.Stat(envfile); err != nil && os.IsNotExist(err) {
return true
}
return false return false
} }
func appSourceExists() bool { // IsYaoApp Check if the directory is a yao app
func IsYaoApp(root string) bool {
appsource := appSource()
if strings.HasSuffix(appsource, ".yaz") || strings.HasPrefix(appsource, "::binary") {
return true
}
// check app.yao/app.json/app.jsonc
root := appRoot()
appfiles := []string{"app.yao", "app.json", "app.jsonc"} appfiles := []string{"app.yao", "app.json", "app.jsonc"}
exist := false yaoapp := false
for _, appfile := range appfiles { for _, appfile := range appfiles {
appfile = filepath.Join(root, appfile) appfile = filepath.Join(root, appfile)
if _, err := os.Stat(appfile); err == nil { if _, err := os.Stat(appfile); err == nil {
exist = true yaoapp = true
break break
} }
} }
return yaoapp
return exist
} }
// ValidateHosting host ports // IsEmptyDir Check if the directory is empty
func ValidateHosting(option map[string]string) error { func IsEmptyDir(dir string) bool {
if option["YAO_PORT"] == "" { f, err := os.Open(dir)
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
}
// ValidateDB db connection
func ValidateDB(option map[string]string) error {
driver, dsn, err := getDSN(option)
if err != nil { if err != nil {
return fmt.Errorf("连接失败 %s", err.Error()) fmt.Println("Can't open the directory: ", err)
return true
} }
defer f.Close()
m, err := capsule.Add("validate", driver, dsn) files, err := f.Readdir(0)
if err != nil { if err != nil {
return fmt.Errorf("连接失败 %s", err.Error()) return true
} }
return len(files) == 0
conn, err := m.Primary()
if err != nil {
return fmt.Errorf("连接失败 %s", err.Error())
}
err = conn.Ping(2 * time.Second)
if err != nil {
return fmt.Errorf("连接失败 %s", err.Error())
}
return nil
}
func appSource() string {
return os.Getenv("YAO_APP_SOURCE")
} }
func appRoot() string { func appRoot() string {
@ -118,16 +56,14 @@ func appRoot() string {
if root == "" { if root == "" {
path, err := os.Getwd() path, err := os.Getwd()
if err != nil { if err != nil {
printError("无法获取应用目录: %s", err) printError("Can't get the application directory: %s", err)
Stop()
} }
root = path root = path
} }
root, err := filepath.Abs(root) root, err := filepath.Abs(root)
if err != nil { if err != nil {
printError("无法获取应用目录: %s", err) printError("Can't get the application directory: %s", err)
Stop()
} }
return root return root
@ -139,35 +75,3 @@ func getConfig() (config.Config, error) {
cfg := config.LoadFrom(envfile) cfg := config.LoadFrom(envfile)
return cfg, nil 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
}

View file

@ -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
}

View file

@ -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")
}

View file

@ -1,140 +1,23 @@
package setup package setup
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/google/uuid"
"github.com/yaoapp/gou/model" "github.com/yaoapp/gou/model"
"github.com/yaoapp/gou/process" "github.com/yaoapp/gou/process"
v8 "github.com/yaoapp/gou/runtime/v8"
"github.com/yaoapp/kun/log" "github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/config" "github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/data" "github.com/yaoapp/yao/data"
"github.com/yaoapp/yao/engine"
"github.com/yaoapp/yao/studio"
"github.com/yaoapp/yao/widgets/app" "github.com/yaoapp/yao/widgets/app"
) )
// Install app install // Install the app to the root directory
// func Install(root string) error {
// {
// "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 {
dbOption, err := getDBOption(payload) // Copy the init source files
if err != nil { err := makeInit(root)
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)
if err != nil { if err != nil {
return err return err
} }
@ -142,119 +25,27 @@ func Install(payload map[string]map[string]string) error {
return nil return nil
} }
func makeService(root string, host string, port string, studioPort string, lang string) error { // Initialize the installed app
file := filepath.Join(root, ".env") func Initialize(root string, cfg config.Config) error {
err := envSet(file, "YAO_HOST", host)
// Migration
err := makeMigrate()
if err != nil { if err != nil {
return err return err
} }
err = envSet(file, "YAO_PORT", port) // Execute the setup hook
err = makeSetup(cfg)
if err != nil { if err != nil {
return err return err
} }
err = envSet(file, "YAO_STUDIO_PORT", studioPort)
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 return nil
} }
func makeInit(root string) error { func makeInit(root string) error {
if appSourceExists() { if !IsEmptyDir(root) {
return nil return nil
} }
@ -285,7 +76,7 @@ func makeInit(root string) error {
return nil return nil
} }
func makeMigrate(root string, cfg config.Config) error { func makeMigrate() error {
// Do Stuff Here // Do Stuff Here
for _, mod := range model.Models { for _, mod := range model.Models {
@ -308,35 +99,10 @@ func makeMigrate(root string, cfg config.Config) error {
return nil return nil
} }
func makeSetup(root string, cfg config.Config) error { func makeSetup(cfg config.Config) error {
if app.Setting != nil && app.Setting.Setup != "" { 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) p, err := process.Of(app.Setting.Setup, cfg)
if err != nil { if err != nil {
return err return err

View file

@ -3,164 +3,12 @@ package setup
import ( import (
"fmt" "fmt"
"net" "net"
"net/http"
"os" "os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/gin-gonic/gin"
"github.com/yaoapp/yao/config" "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 // URLs get admin url
func URLs(cfg config.Config) ([]string, error) { func URLs(cfg config.Config) ([]string, error) {
@ -176,31 +24,6 @@ func URLs(cfg config.Config) ([]string, error) {
return ips, nil 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{}) { func printError(message string, args ...interface{}) {
fmt.Println(color.RedString(message, args...)) fmt.Println(color.RedString(message, args...))
os.Exit(1) os.Exit(1)