package config import ( "fmt" "os" "path/filepath" webassets "ymhut-box/server/unified-management/web" ) type Check struct { Name string `json:"name"` Status string `json:"status"` Path string `json:"path,omitempty"` Message string `json:"message,omitempty"` } func Preflight(cfg *Config) []Check { checks := []Check{ checkDir("storage", cfg.StorageDir, true), checkParent("sqlite", cfg.Database.SQLitePath), checkDir("update public", cfg.UpdatePublicDir, false), checkDir("update notice", cfg.UpdateNoticeDir, false), checkDir("downloads", cfg.DownloadsDir, false), checkFile("legacy update-info", filepath.Join(cfg.UpdatePublicDir, "update-info.json"), false), checkFile("legacy media-types", filepath.Join(cfg.UpdatePublicDir, "media-types.json"), false), checkFile("version notice index", filepath.Join(cfg.UpdateNoticeDir, "total.json"), false), checkWebBuild("admin web dist", cfg.AdminWebDir, "admin/dist"), checkWebBuild("portal web dist", cfg.PortalWebDir, "portal/dist"), checkWebBuild("setup web dist", cfg.SetupWebDir, "setup/dist"), } return checks } func checkDir(name, path string, create bool) Check { if create { if err := os.MkdirAll(path, 0o750); err != nil { return Check{Name: name, Status: "error", Path: path, Message: err.Error()} } } info, err := os.Stat(path) if err != nil { return Check{Name: name, Status: "missing", Path: path, Message: "directory not found"} } if !info.IsDir() { return Check{Name: name, Status: "error", Path: path, Message: "path is not a directory"} } return Check{Name: name, Status: "ok", Path: path} } func checkParent(name, path string) Check { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o750); err != nil { return Check{Name: name, Status: "error", Path: path, Message: err.Error()} } return Check{Name: name, Status: "ok", Path: path} } func checkFile(name, path string, required bool) Check { info, err := os.Stat(path) if err != nil { status := "missing" if required { status = "error" } return Check{Name: name, Status: status, Path: path, Message: "file not found"} } if info.IsDir() { return Check{Name: name, Status: "error", Path: path, Message: "path is a directory"} } return Check{Name: name, Status: "ok", Path: path} } func checkWebBuild(name, path, embedRoot string) Check { dir := checkDir(name, path, false) if dir.Status != "ok" { if embeddedWebBuildOK(embedRoot) { return Check{Name: name, Status: "ok", Path: path, Message: "using embedded frontend assets"} } dir.Message = "frontend dist missing; run npm install && npm run build, or build release with -tags embed_web" return dir } index := filepath.Join(path, "index.html") if file := checkFile(name+" index", index, true); file.Status != "ok" { if embeddedWebBuildOK(embedRoot) { return Check{Name: name, Status: "ok", Path: path, Message: "disk index missing; using embedded frontend assets"} } return Check{Name: name, Status: "missing", Path: index, Message: "index.html missing; run npm run build"} } assets := filepath.Join(path, "assets") if assetDir := checkDir(name+" assets", assets, false); assetDir.Status != "ok" { if embeddedWebBuildOK(embedRoot) { return Check{Name: name, Status: "ok", Path: path, Message: "disk assets missing; using embedded frontend assets"} } return Check{Name: name, Status: "missing", Path: assets, Message: "assets directory missing; run npm run build"} } return Check{Name: name, Status: "ok", Path: path} } func embeddedWebBuildOK(embedRoot string) bool { if !webassets.Embedded { return false } if _, err := webassets.ReadFile(filepath.ToSlash(filepath.Join(embedRoot, "index.html"))); err != nil { return false } entries, err := webassets.ReadDir(filepath.ToSlash(filepath.Join(embedRoot, "assets"))) if err != nil { return false } for _, entry := range entries { if !entry.IsDir() { return true } } return false } func FormatPreflight(checks []Check) []string { lines := make([]string, 0, len(checks)) for _, check := range checks { line := fmt.Sprintf("[%s] %s", check.Status, check.Name) if check.Path != "" { line += " -> " + check.Path } if check.Message != "" { line += " (" + check.Message + ")" } lines = append(lines, line) } return lines }