// BaMoRT Desktop application entry point. // Uses Wails v2 to wrap the existing Gin HTTP server in a native desktop window. // The backend runs on localhost:8185 (SQLite); the WebView loads the bundled frontend. package main import ( "context" "embed" "io/fs" "log" "os" "time" "bamort/appsystem" "bamort/character" "bamort/config" "bamort/database" "bamort/equipment" "bamort/gamesystem" "bamort/gsmaster" "bamort/importer" "bamort/logger" "bamort/maintenance" "bamort/models" "bamort/pdfrender" "bamort/router" "bamort/transfer" "bamort/user" "github.com/gin-gonic/gin" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) // frontend/dist is populated by `wails build` running the frontend:build command // in wails.json (outputs to ../desktop/frontend/dist via DESKTOP_BUILD=1). // //go:embed all:frontend/dist var assets embed.FS // App is the Wails application struct. Lifecycle hooks are attached to it. type App struct { ctx context.Context } // NewApp creates the Wails application instance. func NewApp() *App { return &App{} } // startup is called by Wails just before the WebView window is shown. // It starts the Gin HTTP server in the background so the frontend can reach it. func (a *App) startup(ctx context.Context) { a.ctx = ctx startGinServer() } // GetAPIBaseURL returns the HTTP server address for the frontend to connect to. // This allows the API port to be configured at runtime via .env without rebuilding. func (a *App) GetAPIBaseURL() string { // GetServerAddress returns ":port", so we need to add localhost return "http://localhost" + config.Cfg.GetServerAddress() } // startGinServer initialises the database, runs migrations, and starts the // Gin HTTP server on the configured port (default 8185) in a goroutine. func startGinServer() { cfg := config.Cfg logger.Info("BaMoRT Desktop Server wird gestartet...") logger.Info("Environment: %s", cfg.Environment) logger.Info("Database: %s @ %s", cfg.DatabaseType, cfg.DatabaseURL) gin.SetMode(gin.ReleaseMode) // Connect to SQLite database. database.ConnectDatabase() // Auto-migrate all modules so a fresh SQLite file gets a complete schema. if err := models.MigrateStructure(); err != nil { logger.Warn("models migration: %s", err.Error()) } if err := user.MigrateStructure(); err != nil { logger.Warn("user migration: %s", err.Error()) } if err := gamesystem.MigrateStructure(); err != nil { logger.Warn("gamesystem migration: %s", err.Error()) } if err := database.MigrateStructure(); err != nil { logger.Warn("database migration: %s", err.Error()) } // Initialise PDF templates (source from ./default_templates if present). if err := pdfrender.InitializeTemplates("./default_templates", cfg.TemplatesDir); err != nil { logger.Warn("template init: %s", err.Error()) } r := gin.Default() router.SetupGin(r) protected := router.BaseRouterGrp(r) user.RegisterRoutes(protected) gsmaster.RegisterRoutes(protected) character.RegisterRoutes(protected) equipment.RegisterRoutes(protected) maintenance.RegisterRoutes(protected) importer.RegisterRoutes(protected) pdfrender.RegisterRoutes(protected) transfer.RegisterRoutes(protected) appsystem.RegisterRoutes(protected) pdfrender.RegisterPublicRoutes(r) appsystem.RegisterPublicRoutes(r) logger.Info("Desktop API server starting on %s", cfg.GetServerAddress()) go func() { if err := r.Run(cfg.GetServerAddress()); err != nil { log.Printf("Gin server error: %v", err) } }() // Brief pause to let the HTTP server bind before the WebView starts loading. time.Sleep(150 * time.Millisecond) } func main() { // Apply desktop defaults BEFORE anything uses config.Cfg. // config.init() already ran (at import time) and may have picked up .env; // here we ensure the critical SQLite settings exist even without a .env file. if os.Getenv("DATABASE_TYPE") == "" { _ = os.Setenv("DATABASE_TYPE", "sqlite") } if os.Getenv("DATABASE_URL") == "" { _ = os.Setenv("DATABASE_URL", "./bamort-desktop.db") } if os.Getenv("ENVIRONMENT") == "" { _ = os.Setenv("ENVIRONMENT", "desktop") } if os.Getenv("API_PORT") == "" { _ = os.Setenv("API_PORT", "8185") } if os.Getenv("TEMPLATES_DIR") == "" { _ = os.Setenv("TEMPLATES_DIR", "./templates") } if os.Getenv("EXPORT_TEMP_DIR") == "" { _ = os.Setenv("EXPORT_TEMP_DIR", "./export_temp") } // Reload config so the above env vars take effect (init() may have run first). config.Cfg = config.LoadConfig() // Sub-FS rooted at frontend/dist so Wails serves it from /. frontendFS, err := fs.Sub(assets, "frontend/dist") if err != nil { log.Fatal("frontend/dist embed error:", err) } app := NewApp() if err := wails.Run(&options.App{ Title: "BaMoRT", Width: 1400, Height: 900, MinWidth: 1024, MinHeight: 768, OnStartup: app.startup, Bind: []interface{}{ app, }, AssetServer: &assetserver.Options{ Assets: frontendFS, }, }); err != nil { log.Fatal(err) } }