Files
tyto/backend/cmd/server/main.go
vikingowl c0e678931d feat: add database layer with SQLite and PostgreSQL support
Database Package (internal/database/):
- Database interface abstraction for multiple backends
- SQLite implementation with pure Go driver (no CGO)
- PostgreSQL implementation with connection pooling
- Factory pattern for creating database from config
- Tiered retention with automatic aggregation:
  - Raw metrics: 24h (5s resolution)
  - 1-minute aggregation: 7 days
  - 5-minute aggregation: 30 days
  - Hourly aggregation: 1 year

Schema includes:
- agents: registration, status, certificates
- users: local + LDAP authentication
- roles: RBAC with permissions JSON
- sessions: token-based authentication
- metrics_*: time-series with aggregation
- alerts: triggered alerts with acknowledgment

Configuration Updates:
- DatabaseConfig with SQLite path and PostgreSQL settings
- RetentionConfig for customizing data retention
- Environment variables: TYTO_DB_*, TYTO_DB_CONNECTION_STRING
- Default SQLite at /var/lib/tyto/tyto.db

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 08:18:48 +01:00

184 lines
4.6 KiB
Go

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")
}