package main import ( "fmt" "log" "net/http" "os" "path/filepath" "3588AdminBackend/internal/api" "3588AdminBackend/internal/config" "3588AdminBackend/internal/service" "3588AdminBackend/internal/storage" "3588AdminBackend/internal/web" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) func main() { cfgPath := "managerd.json" if len(os.Args) > 1 { cfgPath = os.Args[1] } cfg, err := config.LoadConfig(cfgPath) if err != nil { log.Fatalf("failed to load config: %v", err) } // Initialize Services agentClient := service.NewAgentClient(cfg) store, err := storage.OpenSQLite(cfg.DBPathOrDefault()) if err != nil { log.Fatalf("failed to open storage: %v", err) } regSvc := service.NewRegistryService(cfg, agentClient, storage.NewDevicesRepo(store.DB())) discoSvc := service.NewDiscoveryService(cfg, regSvc) regSvc.SetDiscovery(discoSvc) defer store.Close() taskRepo := storage.NewTasksRepo(store.DB()) assetsRepo := storage.NewAssetsRepo(store.DB()) modelsRepo := storage.NewModelsRepo(store.DB()) if imported, err := service.ImportStandardTemplatesFromDir(assetsRepo, filepath.Join("templates", "standard_templates")); err != nil { log.Fatalf("import standard templates: %v", err) } else if imported > 0 { log.Printf("imported %d standard templates", imported) } standardModelsDir := filepath.Join("models", "standard_models") modelSvc := service.NewModelManagementService(modelsRepo) if err := modelSvc.SyncStandardModelsFromDirectory(standardModelsDir); err != nil { log.Fatalf("sync standard models: %v", err) } resourcesRepo := storage.NewResourcesRepo(store.DB()) resourceSvc := service.NewResourceManagementService(resourcesRepo) standardResourcesDir := filepath.Join("resources", "standard_resources") if err := resourceSvc.SyncStandardResourcesFromDirectory(standardResourcesDir); err != nil { log.Printf("sync standard resources: %v", err) } stateRepo := storage.NewDeviceConfigStateRepo(store.DB()) auditRepo := storage.NewAuditLogsRepo(store.DB()) taskSvc := service.NewTaskService(cfg, agentClient, regSvc, taskRepo) taskSvc.SetStandardModels(modelsRepo, standardModelsDir) taskSvc.SetStandardResources(resourcesRepo) taskSvc.SetDeviceConfigStateRepo(stateRepo) taskSvc.SetAuditLogRepo(auditRepo) if err := taskSvc.LoadPersistedTasks(); err != nil { log.Printf("load persisted tasks: %v", err) } tplSvc := service.NewTemplateService(cfg) h := api.NewHandler(discoSvc, regSvc, agentClient, taskSvc, tplSvc) r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"http://localhost:5173", "http://127.0.0.1:5173"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-RK-Token", "X-Model-Sha256"}, MaxAge: 300, })) r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) }) r.Get("/openapi.json", api.OpenAPI) r.Get("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/ui", http.StatusFound) }) ui, err := web.NewUI(discoSvc, regSvc, agentClient, taskSvc, tplSvc, service.NewConfigPreviewService(cfg, assetsRepo)) if err != nil { log.Fatalf("failed to init ui: %v", err) } ui.SetStateRepo(stateRepo) ui.SetAuditRepo(auditRepo) ui.SetDBPath(cfg.DBPathOrDefault()) ui.SetResourcesRepo(resourcesRepo) uiRouter, err := ui.Routes() if err != nil { log.Fatalf("failed to init ui routes: %v", err) } r.Mount("/ui", http.StripPrefix("/ui", uiRouter)) // API Routes r.Route("/api", func(r chi.Router) { r.Post("/discovery/search", h.Search) r.Get("/devices", h.ListDevices) r.Post("/devices", h.CreateDevice) r.Get("/devices/{id}", h.GetDevice) // Proxy routes for device actions r.Get("/devices/{id}/info", h.ProxyAgent) r.Get("/devices/{id}/config/status", h.ProxyAgent) r.Put("/devices/{id}/config/candidate", h.ProxyAgent) r.Post("/devices/{id}/config/candidate/apply", h.ProxyAgent) r.Post("/devices/{id}/reload", h.ProxyAgent) r.Post("/devices/{id}/rollback", h.ProxyAgent) r.Get("/devices/{id}/graphs", h.ProxyAgent) r.Get("/devices/{id}/graphs/{name}", h.ProxyAgent) r.Get("/devices/{id}/logs", h.ProxyAgent) r.Post("/devices/{id}/config/apply", h.ProxyAgent) r.Get("/devices/{id}/models", h.ProxyAgent) r.Post("/devices/{id}/media-server/start", h.ProxyAgent) r.Post("/devices/{id}/media-server/restart", h.ProxyAgent) r.Post("/devices/{id}/media-server/stop", h.ProxyAgent) r.Get("/devices/{id}/media-server/status", h.ProxyAgent) // Generic passthrough for device agent /v1/* (covers config/ui/*, face-gallery, etc.) r.Handle("/devices/{id}/v1/*", http.HandlerFunc(h.ProxyAgentV1)) // Task routes r.Post("/tasks", h.CreateTask) r.Get("/tasks", h.ListTasks) r.Get("/tasks/{id}/events", h.TaskEvents) // Template routes r.Get("/templates", h.ListTemplates) r.Get("/templates/{name}", h.GetTemplate) // Model routes r.Post("/devices/{id}/models/upload", h.UploadModel) }) fmt.Printf("Starting managerd on %s\n", cfg.Listen) if err := http.ListenAndServe(cfg.Listen, r); err != nil { log.Fatalf("failed to start server: %v", err) } }