package main import ( "context" "log" "os" "os/signal" "syscall" "tyto/internal/agent" "tyto/internal/api" "tyto/internal/config" "tyto/internal/server" "tyto/internal/sse" ) func main() { cfg := config.Load() switch { case cfg.IsAgent(): runAgent(cfg) case cfg.IsServer(): runServer(cfg) default: runStandalone(cfg) } } // runStandalone starts Tyto in single-host monitoring mode. // This is the default mode with no database or agent support. func runStandalone(cfg *config.Config) { log.Printf("Starting Tyto in standalone mode on port %s", cfg.Port) log.Printf("Reading from: proc=%s, sys=%s", cfg.ProcPath, cfg.SysPath) log.Printf("Default refresh interval: %s", cfg.RefreshInterval) if cfg.AuthEnabled { log.Printf("Basic authentication enabled for user: %s", cfg.AuthUser) } if cfg.TLSEnabled { log.Printf("TLS enabled with cert: %s", cfg.TLSCertFile) } broker := sse.NewBroker(cfg) go broker.Run() server := api.NewServer(cfg, broker) var err error if cfg.TLSEnabled { log.Printf("Starting HTTPS server on port %s", cfg.Port) err = server.RunTLS(cfg.TLSCertFile, cfg.TLSKeyFile) } else { err = server.Run() } if err != nil { log.Fatalf("Failed to start server: %v", err) } } // runServer starts Tyto in full server mode with database, agents, and auth. func runServer(cfg *config.Config) { log.Printf("Starting Tyto in server mode on port %s", cfg.Port) log.Printf("gRPC port for agents: %d", cfg.Server.GRPCPort) log.Printf("Database: %s", cfg.Database.Type) // Set up signal handling _, cancel := context.WithCancel(context.Background()) defer cancel() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) // Initialize agent registry registryPath := "/var/lib/tyto/agents.json" if cfg.Database.Path != "" { registryPath = cfg.Database.Path + ".agents.json" } registry := server.NewRegistry(registryPath) log.Printf("Agent registry initialized: %s", registryPath) // Initialize Hub hubConfig := &server.HubConfig{ RequireApproval: cfg.Server.Registration.RequireApproval, AutoApprove: cfg.Server.Registration.AutoEnabled && !cfg.Server.Registration.RequireApproval, } hub := server.NewHub(registry, hubConfig) hub.Start() defer hub.Stop() log.Println("Agent hub started") // Initialize SSE bridge for multi-device streaming bridge := server.NewSSEBridge(hub) bridge.Start() defer bridge.Stop() // Initialize gRPC server for agent connections grpcServer, err := server.NewGRPCServer(hub, &cfg.Server) if err != nil { log.Fatalf("Failed to create gRPC server: %v", err) } // Start gRPC server in background go func() { log.Printf("Starting gRPC server on port %d", cfg.Server.GRPCPort) if err := grpcServer.Start(cfg.Server.GRPCPort); err != nil { log.Printf("gRPC server error: %v", err) } }() defer grpcServer.Stop() // Initialize SSE broker for local metrics (also runs in server mode) broker := sse.NewBroker(cfg) go broker.Run() // Initialize HTTP API server with agent management apiServer := api.NewServer(cfg, broker) // Add agent API routes agentAPI := api.NewAgentAPI(registry, hub) agentAPI.RegisterRoutes(apiServer.Router().Group("/api/v1")) // Start HTTP server in background go func() { var err error if cfg.TLSEnabled { log.Printf("Starting HTTPS server on port %s", cfg.Port) err = apiServer.RunTLS(cfg.TLSCertFile, cfg.TLSKeyFile) } else { log.Printf("Starting HTTP server on port %s", cfg.Port) err = apiServer.Run() } if err != nil { log.Printf("HTTP server error: %v", err) } }() // Wait for shutdown signal <-sigCh log.Println("Shutting down server...") cancel() } // runAgent starts Tyto as a lightweight agent that reports to a central server. func runAgent(cfg *config.Config) { if cfg.Agent.ID == "" { log.Fatal("Agent ID is required in agent mode (set TYTO_AGENT_ID)") } if cfg.Agent.ServerURL == "" { log.Fatal("Server URL is required in agent mode (set TYTO_SERVER_URL)") } log.Printf("Starting Tyto agent '%s'", cfg.Agent.ID) log.Printf("Reporting to: %s", cfg.Agent.ServerURL) log.Printf("Collection interval: %s", cfg.Agent.Interval) // Set up signal handling ctx, cancel := context.WithCancel(context.Background()) defer cancel() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) // Create agent a := agent.New(cfg) // Handle shutdown signal go func() { <-sigCh log.Println("Received shutdown signal, stopping agent...") a.Stop() cancel() }() // Run agent if err := a.Run(ctx); err != nil && err != context.Canceled { log.Fatalf("Agent error: %v", err) } log.Println("Agent stopped") }