Files
OrchestrAD/backend/internal/cli/cli.go
T
GraceSolutions 2376daf91c feat(startup): wire runner, engine, and scheduler into foreground mode
Derives a 32-byte AES key from the configured secret via SHA-256, constructs
the ConnectionService, Runner, and Engine, and starts the Scheduler so
enabled rules fire automatically. Injects the shared services into the HTTP
server via a new Dependencies struct so API handlers can reuse them.
2026-04-23 12:14:50 -04:00

200 lines
5.4 KiB
Go

// Package cli implements command-line interface handlers for OrchestrAD
package cli
import (
"context"
"crypto/sha256"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/Grace-Solutions/OrchestrAD/internal/config"
"github.com/Grace-Solutions/OrchestrAD/internal/crypto"
"github.com/Grace-Solutions/OrchestrAD/internal/db"
"github.com/Grace-Solutions/OrchestrAD/internal/logging"
"github.com/Grace-Solutions/OrchestrAD/internal/repository"
"github.com/Grace-Solutions/OrchestrAD/internal/rules/engine"
"github.com/Grace-Solutions/OrchestrAD/internal/rules/runner"
"github.com/Grace-Solutions/OrchestrAD/internal/scheduler"
"github.com/Grace-Solutions/OrchestrAD/internal/server"
"github.com/Grace-Solutions/OrchestrAD/internal/services"
)
// RunInit initializes the application: validates config, initializes DB, runs migrations
func RunInit() error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
logger := logging.New(cfg.Logging)
logger.Info("CLI", "Initializing OrchestrAD...")
// Initialize database
database, err := db.New(cfg.Database, logger)
if err != nil {
return fmt.Errorf("initializing database: %w", err)
}
defer database.Close()
// Run migrations
if err := database.Migrate(); err != nil {
return fmt.Errorf("running migrations: %w", err)
}
logger.Info("CLI", "Initialization complete")
return nil
}
// RunForeground runs the application in foreground mode
func RunForeground() error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
logger := logging.New(cfg.Logging)
logger.Info("CLI", "Starting OrchestrAD in foreground mode...")
// Initialize database
database, err := db.New(cfg.Database, logger)
if err != nil {
return fmt.Errorf("initializing database: %w", err)
}
defer database.Close()
// Run migrations
if err := database.Migrate(); err != nil {
return fmt.Errorf("running migrations: %w", err)
}
// Derive a 32-byte AES key from the configured secret key
keyHash := sha256.Sum256(cfg.SecretKey)
encryptor, err := crypto.NewEncryptor(keyHash[:])
if err != nil {
return fmt.Errorf("initializing encryptor: %w", err)
}
// Build services, repositories, and the execution pipeline
connService := services.NewConnectionService(database.Conn(), encryptor, logger)
ruleRunner := runner.New(database.Conn(), connService, logger)
deps := server.Dependencies{
Runner: ruleRunner,
Engine: engine.NewEngine(logger),
ConnService: connService,
RuleRepo: repository.NewRuleRepository(database.Conn()),
ConnRepo: repository.NewConnectionRepository(database.Conn()),
RunRepo: repository.NewRuleRunRepository(database.Conn()),
}
// Setup graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Start the scheduler so enabled rules fire automatically
sched := scheduler.New(database.Conn(), ruleRunner, logger)
if err := sched.Start(ctx); err != nil {
return fmt.Errorf("starting scheduler: %w", err)
}
defer sched.Stop()
// Create HTTP server
srv := server.New(cfg, database, deps, logger)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
logger.Info("CLI", "Shutdown signal received, stopping server...")
cancel()
}()
return srv.Run(ctx)
}
// RunInstall installs the application as a system service
func RunInstall() error {
return fmt.Errorf("service install not yet implemented for this platform")
}
// RunUninstall removes the system service
func RunUninstall() error {
return fmt.Errorf("service uninstall not yet implemented for this platform")
}
// RunStart starts the installed service
func RunStart() error {
return fmt.Errorf("service start not yet implemented for this platform")
}
// RunStop stops the installed service
func RunStop() error {
return fmt.Errorf("service stop not yet implemented for this platform")
}
// RunMigrate applies database migrations
func RunMigrate() error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
logger := logging.New(cfg.Logging)
logger.Info("CLI", "Running database migrations...")
database, err := db.New(cfg.Database, logger)
if err != nil {
return fmt.Errorf("initializing database: %w", err)
}
defer database.Close()
if err := database.Migrate(); err != nil {
return fmt.Errorf("running migrations: %w", err)
}
logger.Info("CLI", "Migrations complete")
return nil
}
// RunBackup creates a manual database backup
func RunBackup() error {
return fmt.Errorf("backup not yet implemented")
}
// RunRestore restores the database from a backup file
func RunRestore(filepath string) error {
return fmt.Errorf("restore not yet implemented: %s", filepath)
}
// RunDoctor validates system configuration and health
func RunDoctor() error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("config validation failed: %w", err)
}
logger := logging.New(cfg.Logging)
logger.Info("Doctor", "Running system health checks...")
// Check database
database, err := db.New(cfg.Database, logger)
if err != nil {
logger.Error("Doctor", "Database check failed: %v", err)
} else {
logger.Info("Doctor", "Database: OK")
database.Close()
}
// Check secret key
if len(cfg.SecretKey) < 32 {
logger.Warn("Doctor", "Secret key should be at least 32 bytes")
} else {
logger.Info("Doctor", "Secret key: OK")
}
logger.Info("Doctor", "Health checks complete")
return nil
}