Files
tyto/backend/internal/database/factory.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

91 lines
2.2 KiB
Go

// Package database provides database factory for creating the appropriate backend.
package database
import (
"fmt"
"os"
"path/filepath"
"tyto/internal/config"
)
// New creates a new database connection based on configuration.
func New(cfg *config.DatabaseConfig) (Database, error) {
retention := DefaultRetentionConfig()
// Override retention from config if provided
if cfg.Retention.RawRetention > 0 {
retention.RawRetention = cfg.Retention.RawRetention
}
if cfg.Retention.OneMinuteRetention > 0 {
retention.OneMinuteRetention = cfg.Retention.OneMinuteRetention
}
if cfg.Retention.FiveMinuteRetention > 0 {
retention.FiveMinuteRetention = cfg.Retention.FiveMinuteRetention
}
if cfg.Retention.HourlyRetention > 0 {
retention.HourlyRetention = cfg.Retention.HourlyRetention
}
switch cfg.Type {
case "sqlite", "":
return newSQLiteFromConfig(cfg, retention)
case "postgres", "postgresql":
return newPostgresFromConfig(cfg, retention)
default:
return nil, fmt.Errorf("unsupported database type: %s", cfg.Type)
}
}
func newSQLiteFromConfig(cfg *config.DatabaseConfig, retention RetentionConfig) (*SQLiteDB, error) {
path := cfg.Path
if path == "" {
path = "tyto.db"
}
// Ensure directory exists
dir := filepath.Dir(path)
if dir != "" && dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("create database directory: %w", err)
}
}
db, err := NewSQLiteDB(path, retention)
if err != nil {
return nil, err
}
// Run migrations
if err := db.Migrate(); err != nil {
db.Close()
return nil, fmt.Errorf("migrate: %w", err)
}
return db, nil
}
func newPostgresFromConfig(cfg *config.DatabaseConfig, retention RetentionConfig) (*PostgresDB, error) {
connStr := cfg.ConnectionString
if connStr == "" {
// Build connection string from individual fields
connStr = fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Database, cfg.SSLMode,
)
}
db, err := NewPostgresDB(connStr, retention)
if err != nil {
return nil, err
}
// Run migrations
if err := db.Migrate(); err != nil {
db.Close()
return nil, fmt.Errorf("migrate: %w", err)
}
return db, nil
}