183 lines
6.0 KiB
Go
183 lines
6.0 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
webassets "ymhut-box/server/unified-management/web"
|
|
)
|
|
|
|
const defaultUpdateInfoJSON = `{
|
|
"app_version": "0.0.0",
|
|
"download_url": "",
|
|
"update_notes": {},
|
|
"last_update_notes": {},
|
|
"release_notes": "",
|
|
"release_notes_md": "",
|
|
"last_updated": ""
|
|
}
|
|
`
|
|
|
|
const defaultMediaTypesJSON = `{
|
|
"layout_version": "1.0.0",
|
|
"last_updated": "",
|
|
"ui_config": {},
|
|
"categories": []
|
|
}
|
|
`
|
|
|
|
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, true),
|
|
checkDir("update notice", cfg.UpdateNoticeDir, true),
|
|
checkDir("downloads", cfg.DownloadsDir, true),
|
|
checkSeedFile("legacy update-info", filepath.Join(cfg.UpdatePublicDir, "update-info.json"), []byte(defaultUpdateInfoJSON)),
|
|
checkSeedFile("legacy media-types", filepath.Join(cfg.UpdatePublicDir, "media-types.json"), []byte(defaultMediaTypesJSON)),
|
|
checkNoticeIndex("version notice index", filepath.Join(cfg.UpdateNoticeDir, "total.json")),
|
|
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 checkNoticeIndex(name, path string) Check {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return Check{Name: name, Status: "ok", Path: path}
|
|
} else if !os.IsNotExist(err) {
|
|
return Check{Name: name, Status: "error", Path: path, Message: err.Error()}
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
|
|
return Check{Name: name, Status: "error", Path: path, Message: err.Error()}
|
|
}
|
|
data := []byte("{\n \"schema_version\": 1,\n \"product\": \"YMhut Box\",\n \"versions\": []\n}\n")
|
|
if err := os.WriteFile(path, data, 0o640); err != nil {
|
|
return Check{Name: name, Status: "error", Path: path, Message: err.Error()}
|
|
}
|
|
return Check{Name: name, Status: "ok", Path: path, Message: "created empty notice index"}
|
|
}
|
|
|
|
func checkSeedFile(name, path string, data []byte) Check {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return Check{Name: name, Status: "ok", Path: path}
|
|
} else if !os.IsNotExist(err) {
|
|
return Check{Name: name, Status: "error", Path: path, Message: err.Error()}
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
|
|
return Check{Name: name, Status: "error", Path: path, Message: err.Error()}
|
|
}
|
|
if err := os.WriteFile(path, data, 0o640); err != nil {
|
|
return Check{Name: name, Status: "error", Path: path, Message: err.Error()}
|
|
}
|
|
return Check{Name: name, Status: "ok", Path: path, Message: "created default compatibility JSON"}
|
|
}
|
|
|
|
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(cfg *Config, 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 += " -> " + relativePath(cfg.BaseDir, check.Path)
|
|
}
|
|
if check.Message != "" {
|
|
line += " (" + check.Message + ")"
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
return lines
|
|
}
|