Enhance tool inspection and remove studio configuration
- Add external tool inspection functionality in the engine, allowing detection of tools like FFmpeg, PDF converters, and Docker. - Update the `inspect` command to include detected tools in the output. - Remove the studio configuration from the application, simplifying the configuration structure and related code. - Clean up unused studio-related code in various files, including login processing and configuration management.
This commit is contained in:
parent
85159f49dd
commit
ce137b34f5
10 changed files with 195 additions and 77 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -65,3 +65,4 @@ sandbox/proxy/claude-proxy-linux-*
|
|||
release/*
|
||||
sandbox/TODO-VNC.md
|
||||
sandbox/docker/chrome/PLAN.md
|
||||
sandbox/DESIGN-REMOTE.md
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/kun/utils"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/engine"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
|
|
@ -14,10 +15,14 @@ var inspectCmd = &cobra.Command{
|
|||
Long: L("Show app configure"),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Boot()
|
||||
engine.InspectExtTools()
|
||||
res := maps.Map{
|
||||
"version": share.VERSION,
|
||||
"config": config.Conf,
|
||||
}
|
||||
if share.Tools != nil {
|
||||
res["tools"] = share.Tools
|
||||
}
|
||||
utils.Dump(res)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
15
cmd/root.go
15
cmd/root.go
|
|
@ -104,18 +104,6 @@ var rootCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
var studioCmd = &cobra.Command{
|
||||
Use: "studio",
|
||||
Short: "Yao Studio CLI",
|
||||
Long: `Yao Studio CLI`,
|
||||
CompletionOptions: cobra.CompletionOptions{
|
||||
DisableDefaultCmd: true,
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
var suiCmd = &cobra.Command{
|
||||
Use: "sui",
|
||||
Short: L("SUI Template Engine"),
|
||||
|
|
@ -143,8 +131,6 @@ var agentCmd = &cobra.Command{
|
|||
// Command initialize
|
||||
func init() {
|
||||
|
||||
// studioCmd.AddCommand(studio.RunCmd)
|
||||
|
||||
// Sui
|
||||
suiCmd.AddCommand(sui.WatchCmd)
|
||||
suiCmd.AddCommand(sui.BuildCmd)
|
||||
|
|
@ -166,7 +152,6 @@ func init() {
|
|||
// socketCmd,
|
||||
// websocketCmd,
|
||||
// packCmd,
|
||||
// studioCmd,
|
||||
suiCmd,
|
||||
agentCmd,
|
||||
// upgradeCmd,
|
||||
|
|
|
|||
24
cmd/start.go
24
cmd/start.go
|
|
@ -169,7 +169,6 @@ var startCmd = &cobra.Command{
|
|||
printConnectors(false)
|
||||
printStores(false)
|
||||
printMCPs(false)
|
||||
// printStudio(false, host)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -398,29 +397,6 @@ func printStores(silent bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func printStudio(silent bool, host string) {
|
||||
|
||||
if silent {
|
||||
log.Info("[Studio] http://%s:%d", host, config.Conf.Studio.Port)
|
||||
if config.Conf.Studio.Auto {
|
||||
log.Info("[Studio] Secret: %s", config.Conf.Studio.Secret)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(color.WhiteString("\n---------------------------------"))
|
||||
fmt.Println(color.WhiteString(L("Yao Studio Server")))
|
||||
fmt.Println(color.WhiteString("---------------------------------"))
|
||||
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.Print(color.CyanString("SECRET: "))
|
||||
fmt.Print(color.WhiteString(" %s\n", config.Conf.Studio.Secret))
|
||||
}
|
||||
}
|
||||
|
||||
func printSchedules(silent bool) {
|
||||
|
||||
if len(schedule.Schedules) == 0 {
|
||||
|
|
|
|||
|
|
@ -5,15 +5,12 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/crypto"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
|
|
@ -138,16 +135,6 @@ func LoadWithRoot(root string) Config {
|
|||
cfg.AppSource = cfg.Root
|
||||
}
|
||||
|
||||
// Studio Secret
|
||||
if cfg.Studio.Secret == "" {
|
||||
v, err := crypto.Hash(crypto.HashTypes["SHA256"], uuid.New().String())
|
||||
if err != nil {
|
||||
exception.New("Can't gengrate studio secret %s", 500, err.Error()).Throw()
|
||||
}
|
||||
cfg.Studio.Secret = strings.ToUpper(v)
|
||||
cfg.Studio.Auto = true
|
||||
}
|
||||
|
||||
// DataRoot
|
||||
if cfg.DataRoot == "" {
|
||||
cfg.DataRoot = filepath.Join(cfg.Root, "data")
|
||||
|
|
|
|||
|
|
@ -23,18 +23,10 @@ type Config struct {
|
|||
DB Database `json:"db,omitempty"` // The database config
|
||||
AllowFrom []string `json:"allowfrom,omitempty" envSeparator:"|" env:"YAO_ALLOW_FROM"` // Domain list the separator is |
|
||||
Session Session `json:"session,omitempty"` // Session Config
|
||||
Studio Studio `json:"studio,omitempty"` // Studio config
|
||||
Runtime Runtime `json:"runtime,omitempty"` // Runtime config
|
||||
Trace Trace `json:"trace,omitempty"` // Trace config
|
||||
}
|
||||
|
||||
// Studio the studio config
|
||||
type Studio struct {
|
||||
Port int `json:"studio_port,omitempty" env:"YAO_STUDIO_PORT" envDefault:"5077"` // Studio port
|
||||
Secret string `json:"studio_secret,omitempty" env:"YAO_STUDIO_SECRET"` // Studio Secret, if does not set, auto-generate a secret
|
||||
Auto bool `json:"-"`
|
||||
}
|
||||
|
||||
// Database 数据库配置
|
||||
type Database struct {
|
||||
Driver string `json:"driver,omitempty" env:"YAO_DB_DRIVER" envDefault:"sqlite3"` // 数据库驱动 sqlite3| mysql| postgres
|
||||
|
|
|
|||
149
engine/load.go
149
engine/load.go
|
|
@ -4,12 +4,15 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/yaoapp/gou/application"
|
||||
"github.com/yaoapp/gou/ffmpeg"
|
||||
"github.com/yaoapp/gou/pdf"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
"github.com/yaoapp/yao/agent"
|
||||
|
|
@ -139,6 +142,12 @@ func Load(cfg config.Config, options LoadOption, progressCallback ...func(string
|
|||
warnings = append(warnings, Warning{Widget: "Connector", Error: err})
|
||||
}
|
||||
|
||||
// Inspect external tools (silent, non-fatal)
|
||||
loadStep("ExtTools", func() error {
|
||||
InspectExtTools()
|
||||
return nil
|
||||
}, callback)
|
||||
|
||||
// Load FileSystem
|
||||
err = loadStep("FileSystem", func() error {
|
||||
return fs.Load(cfg)
|
||||
|
|
@ -755,6 +764,146 @@ func loadApp(root string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InspectExtTools performs silent detection of external tools (ffmpeg, pdf converters, docker)
|
||||
// and stores the results in share.Tools for later access via Inspect / settings API.
|
||||
// Exported so it can be called from both engine.Load() and cmd/inspect.go.
|
||||
func InspectExtTools() {
|
||||
tools := &share.ExtTools{}
|
||||
|
||||
// Inspect ffmpeg/ffprobe
|
||||
ffmpegStatus := ffmpeg.Inspect()
|
||||
if s, ok := ffmpegStatus["ffmpeg"]; ok {
|
||||
tools.FFmpeg = &share.ExtToolInfo{
|
||||
Name: s.Name,
|
||||
Available: s.Available,
|
||||
Path: s.Path,
|
||||
Version: s.Version,
|
||||
EnvVar: s.EnvVar,
|
||||
Error: s.Error,
|
||||
}
|
||||
}
|
||||
if s, ok := ffmpegStatus["ffprobe"]; ok {
|
||||
tools.FFprobe = &share.ExtToolInfo{
|
||||
Name: s.Name,
|
||||
Available: s.Available,
|
||||
Path: s.Path,
|
||||
Version: s.Version,
|
||||
EnvVar: s.EnvVar,
|
||||
Error: s.Error,
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect PDF tools
|
||||
pdfStatus := pdf.Inspect()
|
||||
if s, ok := pdfStatus["pdftoppm"]; ok {
|
||||
tools.Pdftoppm = &share.ExtToolInfo{
|
||||
Name: s.Name,
|
||||
Available: s.Available,
|
||||
Path: s.Path,
|
||||
Version: s.Version,
|
||||
EnvVar: s.EnvVar,
|
||||
Error: s.Error,
|
||||
}
|
||||
}
|
||||
if s, ok := pdfStatus["mutool"]; ok {
|
||||
tools.Mutool = &share.ExtToolInfo{
|
||||
Name: s.Name,
|
||||
Available: s.Available,
|
||||
Path: s.Path,
|
||||
Version: s.Version,
|
||||
EnvVar: s.EnvVar,
|
||||
Error: s.Error,
|
||||
}
|
||||
}
|
||||
if s, ok := pdfStatus["imagemagick"]; ok {
|
||||
tools.ImageMagick = &share.ExtToolInfo{
|
||||
Name: s.Name,
|
||||
Available: s.Available,
|
||||
Path: s.Path,
|
||||
Version: s.Version,
|
||||
EnvVar: s.EnvVar,
|
||||
Error: s.Error,
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect Docker
|
||||
tools.Docker = inspectDocker()
|
||||
|
||||
share.Tools = tools
|
||||
}
|
||||
|
||||
// inspectDocker silently detects Docker availability, version, and host configuration.
|
||||
// Returns real runtime info: what Docker is actually connected to, not just env vars.
|
||||
func inspectDocker() *share.DockerInfo {
|
||||
sandboxHost := os.Getenv("YAO_SANDBOX_HOST")
|
||||
sandboxMode := os.Getenv("YAO_SANDBOX_MODE")
|
||||
|
||||
info := &share.DockerInfo{}
|
||||
|
||||
// Try to find docker CLI path
|
||||
dockerPath, err := exec.LookPath("docker")
|
||||
if err != nil {
|
||||
info.Available = false
|
||||
info.Error = "docker not found in PATH"
|
||||
return info
|
||||
}
|
||||
info.Path = dockerPath
|
||||
|
||||
// Get real Docker context info: actual daemon address from docker info
|
||||
cmd := exec.Command(dockerPath, "info", "--format", "{{.ClientInfo.Context}}")
|
||||
ctxOutput, _ := cmd.Output()
|
||||
dockerContext := strings.TrimSpace(string(ctxOutput))
|
||||
|
||||
// Get the actual daemon host from docker context inspect
|
||||
var actualHost string
|
||||
if dockerContext != "" {
|
||||
cmd = exec.Command(dockerPath, "context", "inspect", dockerContext, "--format", "{{.Endpoints.docker.Host}}")
|
||||
hostOutput, err := cmd.Output()
|
||||
if err == nil {
|
||||
actualHost = strings.TrimSpace(string(hostOutput))
|
||||
}
|
||||
}
|
||||
// Fallback: DOCKER_HOST env or let docker figure it out
|
||||
if actualHost == "" {
|
||||
actualHost = os.Getenv("DOCKER_HOST")
|
||||
}
|
||||
info.Host = actualHost
|
||||
|
||||
// Get docker server version (also verifies daemon is reachable)
|
||||
cmd = exec.Command(dockerPath, "version", "--format", "{{.Server.Version}}")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
info.Available = false
|
||||
if actualHost != "" {
|
||||
info.Error = fmt.Sprintf("docker daemon not reachable at %s", actualHost)
|
||||
} else {
|
||||
info.Error = "docker daemon not reachable"
|
||||
}
|
||||
return info
|
||||
}
|
||||
info.Available = true
|
||||
info.Version = strings.TrimSpace(string(output))
|
||||
|
||||
// Determine sandbox mode from real host
|
||||
if sandboxMode != "" {
|
||||
info.Mode = sandboxMode
|
||||
} else if actualHost == "" || strings.HasPrefix(actualHost, "unix://") || strings.HasPrefix(actualHost, "ssh://") || strings.HasPrefix(actualHost, "npipe://") {
|
||||
info.Mode = "local"
|
||||
} else if strings.HasPrefix(actualHost, "tcp://") {
|
||||
info.Mode = "remote"
|
||||
} else {
|
||||
info.Mode = "local"
|
||||
}
|
||||
|
||||
// Yao sandbox env vars
|
||||
info.EnvVars = map[string]string{
|
||||
"YAO_SANDBOX_HOST": sandboxHost,
|
||||
"YAO_SANDBOX_MODE": sandboxMode,
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func printErr(mode, widget string, err error) {
|
||||
message := fmt.Sprintf("[%s] %s", widget, err.Error())
|
||||
if !strings.Contains(message, "does not exists") && !strings.Contains(message, "no such file or directory") && mode == "development" {
|
||||
|
|
|
|||
|
|
@ -38,12 +38,16 @@ func processPing(process *process.Process) interface{} {
|
|||
|
||||
// processInspect 返回系统信息
|
||||
func processInspect(process *process.Process) interface{} {
|
||||
return map[string]interface{}{
|
||||
result := map[string]interface{}{
|
||||
"VERSION": fmt.Sprintf("%s %s", share.VERSION, share.PRVERSION),
|
||||
"CUI": fmt.Sprintf("%s %s", share.CUI, share.PRCUI),
|
||||
"BUILDNAME": share.BUILDNAME,
|
||||
"CONFIG": config.Conf,
|
||||
}
|
||||
if share.Tools != nil {
|
||||
result["TOOLS"] = share.Tools
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// processFavicon 运行模型 MustCreate
|
||||
|
|
|
|||
|
|
@ -151,3 +151,38 @@ type AppRoot struct {
|
|||
Screens string
|
||||
Data string
|
||||
}
|
||||
|
||||
// ExtToolInfo represents the detection result for a single external tool.
|
||||
type ExtToolInfo struct {
|
||||
Name string `json:"name"` // Tool name (e.g. "ffmpeg", "pdftoppm")
|
||||
Available bool `json:"available"` // Whether the tool is available
|
||||
Path string `json:"path,omitempty"` // Resolved executable path
|
||||
Version string `json:"version,omitempty"` // Version string
|
||||
EnvVar string `json:"env_var,omitempty"` // Environment variable name for custom path override
|
||||
Error string `json:"error,omitempty"` // Error message if not available
|
||||
}
|
||||
|
||||
// DockerInfo represents the detection result for Docker.
|
||||
type DockerInfo struct {
|
||||
Available bool `json:"available"` // Whether Docker daemon is reachable
|
||||
Path string `json:"path,omitempty"` // Docker CLI path
|
||||
Version string `json:"version,omitempty"` // Docker server version
|
||||
Mode string `json:"mode,omitempty"` // "local" or "remote"
|
||||
Host string `json:"host,omitempty"` // DOCKER_HOST value (empty = local socket)
|
||||
Error string `json:"error,omitempty"` // Error message if not available
|
||||
EnvVars map[string]string `json:"env_vars,omitempty"` // Related Yao environment variables
|
||||
}
|
||||
|
||||
// ExtTools holds the detection results for all external tools.
|
||||
// Populated during engine.Load() and exposed via utils.app.Inspect.
|
||||
type ExtTools struct {
|
||||
FFmpeg *ExtToolInfo `json:"ffmpeg"`
|
||||
FFprobe *ExtToolInfo `json:"ffprobe"`
|
||||
Pdftoppm *ExtToolInfo `json:"pdftoppm"`
|
||||
Mutool *ExtToolInfo `json:"mutool"`
|
||||
ImageMagick *ExtToolInfo `json:"imagemagick"`
|
||||
Docker *DockerInfo `json:"docker"`
|
||||
}
|
||||
|
||||
// Tools holds the global external tool detection results.
|
||||
var Tools *ExtTools
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/yaoapp/kun/exception"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/helper"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
|
@ -113,20 +112,6 @@ func auth(field string, value string, password string, sid string) maps.Map {
|
|||
session.Global().Expire(time.Duration(token.ExpiresAt)*time.Second).ID(sid).Set("user", row)
|
||||
session.Global().Expire(time.Duration(token.ExpiresAt)*time.Second).ID(sid).Set("issuer", "yao")
|
||||
|
||||
studio := map[string]interface{}{}
|
||||
if config.Conf.Mode == "development" {
|
||||
|
||||
studioToken := helper.JwtMake(id, map[string]interface{}{}, map[string]interface{}{
|
||||
"expires_at": expiresAt,
|
||||
"sid": sid,
|
||||
"issuer": "yao",
|
||||
}, []byte(config.Conf.Studio.Secret))
|
||||
|
||||
studio["port"] = config.Conf.Studio.Port
|
||||
studio["token"] = studioToken.Token
|
||||
studio["expires_at"] = studioToken.ExpiresAt
|
||||
}
|
||||
|
||||
// Get user menus
|
||||
menus := process.New("yao.app.menu").WithSID(sid).Run()
|
||||
return maps.Map{
|
||||
|
|
@ -134,6 +119,5 @@ func auth(field string, value string, password string, sid string) maps.Map {
|
|||
"token": token.Token,
|
||||
"user": row,
|
||||
"menus": menus,
|
||||
"studio": studio,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue