package main import ( "context" "fmt" "log/slog" "os" "runtime" "strings" "time" "vctp/db" "vctp/internal/secrets" "vctp/internal/settings" "vctp/internal/tasks" utils "vctp/internal/utils" "vctp/internal/vcenter" "vctp/log" "vctp/server" "vctp/server/router" "github.com/go-co-op/gocron/v2" "github.com/joho/godotenv" ) var ( bindDisableTls bool sha1ver string // sha1 revision used to build the program buildTime string // when the executable was built cronFrequency time.Duration cronInvFrequency time.Duration cronSnapshotFrequency time.Duration cronAggregateFrequency time.Duration encryptionKey = []byte("5L1l3B5KvwOCzUHMAlCgsgUTRAYMfSpa") ) func main() { // Load data from environment file envFilename := utils.GetFilePath(".env") err := godotenv.Load(envFilename) if err != nil { panic("Error loading .env file") } logger := log.New( log.GetLevel(), log.GetOutput(), ) ctx, cancel := context.WithCancel(context.Background()) // Configure database dbDriver := os.Getenv("DB_DRIVER") if dbDriver == "" { dbDriver = "sqlite" } normalizedDriver := strings.ToLower(strings.TrimSpace(dbDriver)) if normalizedDriver == "" || normalizedDriver == "sqlite3" { normalizedDriver = "sqlite" } dbURL := os.Getenv("DB_URL") if dbURL == "" && normalizedDriver == "sqlite" { dbURL = utils.GetFilePath("db.sqlite3") } database, err := db.New(logger, db.Config{Driver: dbDriver, DSN: dbURL}) if err != nil { logger.Error("Failed to create database", "error", err) os.Exit(1) } defer database.Close() //defer database.DB().Close() if err = db.Migrate(database, dbDriver); err != nil { logger.Error("failed to migrate database", "error", err) os.Exit(1) } // Load settings from yaml settingsFile := os.Getenv("SETTINGS_FILE") if settingsFile == "" { settingsFile = "settings.yaml" } // TODO - how to pass this to the other packages that will need this info? s := settings.New(logger, settingsFile) err = s.ReadYMLSettings() //s, err := settings.ReadYMLSettings(logger, settingsFile) if err != nil { logger.Error("failed to open yaml settings file", "error", err, "filename", settingsFile) //os.Exit(1) } else { logger.Debug("Loaded yaml settings", "contents", s) } // Determine bind IP bindIP := os.Getenv("BIND_IP") if bindIP == "" { bindIP = utils.GetOutboundIP().String() } // Determine bind port bindPort := os.Getenv("BIND_PORT") if bindPort == "" { bindPort = "9443" } bindAddress := fmt.Sprint(bindIP, ":", bindPort) //logger.Info("Will listen on address", "ip", bindIP, "port", bindPort) // Determine bind disable TLS bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS") if bindDisableTlsEnv == "true" { bindDisableTls = true } // Get file names for TLS cert/key tlsCertFilename := os.Getenv("TLS_CERT_FILE") if tlsCertFilename != "" { tlsCertFilename = utils.GetFilePath(tlsCertFilename) } else { tlsCertFilename = "./cert.pem" } tlsKeyFilename := os.Getenv("TLS_KEY_FILE") if tlsKeyFilename != "" { tlsKeyFilename = utils.GetFilePath(tlsKeyFilename) } else { tlsKeyFilename = "./privkey.pem" } // Generate certificate if required if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) { logger.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename) utils.GenerateCerts(tlsCertFilename, tlsKeyFilename) } // Load vcenter credentials from .env a := secrets.New(logger, encryptionKey) vcEp := os.Getenv("VCENTER_PASSWORD") if len(vcEp) == 0 { logger.Error("No vcenter password configured") os.Exit(1) } vcPass, err := a.Decrypt(vcEp) if err != nil { logger.Error("failed to decrypt vcenter credentials. Assuming un-encrypted", "error", err) vcPass = []byte(vcEp) //os.Exit(1) } creds := vcenter.VcenterLogin{ //insecureString := os.Getenv("VCENTER_INSECURE") Username: os.Getenv("VCENTER_USERNAME"), Password: string(vcPass), } // Prepare the task scheduler c, err := gocron.NewScheduler() if err != nil { logger.Error("failed to create scheduler", "error", err) os.Exit(1) } // Pass useful information to the cron jobs ct := &tasks.CronTask{ Logger: logger, Database: database, Settings: s, VcCreds: &creds, } cronFrequencyString := os.Getenv("VCENTER_EVENT_POLLING_SECONDS") if cronFrequencyString != "" { cronFrequency, err = time.ParseDuration(cronFrequencyString) if err != nil { slog.Error("Can't convert VCENTER_EVENT_POLLING_SECONDS value to time duration. Defaulting to 60s", "value", cronFrequencyString, "error", err) cronFrequency = time.Second * 60 } } else { cronFrequency = time.Second * 60 } logger.Debug("Setting VM event polling cronjob frequency to", "frequency", cronFrequency) cronInventoryFrequencyString := os.Getenv("VCENTER_INVENTORY_POLLING_SECONDS") if cronInventoryFrequencyString != "" { cronInvFrequency, err = time.ParseDuration(cronInventoryFrequencyString) if err != nil { slog.Error("Can't convert VCENTER_INVENTORY_POLLING_SECONDS value to time duration. Defaulting to 7200", "value", cronInventoryFrequencyString, "error", err) cronInvFrequency = time.Second * 7200 } } else { cronInvFrequency = time.Second * 7200 } logger.Debug("Setting VM inventory polling cronjob frequency to", "frequency", cronInvFrequency) cronSnapshotFrequencyString := os.Getenv("VCENTER_INVENTORY_SNAPSHOT_SECONDS") if cronSnapshotFrequencyString != "" { cronSnapshotFrequency, err = time.ParseDuration(cronSnapshotFrequencyString) if err != nil { slog.Error("Can't convert VCENTER_INVENTORY_SNAPSHOT_SECONDS value to time duration. Defaulting to 3600", "value", cronSnapshotFrequencyString, "error", err) cronSnapshotFrequency = time.Hour } } else { cronSnapshotFrequency = time.Hour } logger.Debug("Setting VM inventory snapshot cronjob frequency to", "frequency", cronSnapshotFrequency) cronAggregateFrequencyString := os.Getenv("VCENTER_INVENTORY_AGGREGATE_SECONDS") if cronAggregateFrequencyString != "" { cronAggregateFrequency, err = time.ParseDuration(cronAggregateFrequencyString) if err != nil { slog.Error("Can't convert VCENTER_INVENTORY_AGGREGATE_SECONDS value to time duration. Defaulting to 86400", "value", cronAggregateFrequencyString, "error", err) cronAggregateFrequency = time.Hour * 24 } } else { cronAggregateFrequency = time.Hour * 24 } logger.Debug("Setting VM inventory aggregation cronjob frequency to", "frequency", cronAggregateFrequency) // start background processing for events stored in events table startsAt := time.Now().Add(time.Second * 10) job, err := c.NewJob( gocron.DurationJob(cronFrequency), gocron.NewTask(func() { ct.RunVmCheck(ctx, logger) }), gocron.WithSingletonMode(gocron.LimitModeReschedule), gocron.WithStartAt(gocron.WithStartDateTime(startsAt)), ) if err != nil { logger.Error("failed to start event processing cron job", "error", err) os.Exit(1) } logger.Debug("Created event processing cron job", "job", job.ID(), "starting_at", startsAt) // start background checks of vcenter inventory startsAt2 := time.Now().Add(cronInvFrequency) job2, err := c.NewJob( gocron.DurationJob(cronInvFrequency), gocron.NewTask(func() { ct.RunVcenterPoll(ctx, logger) }), gocron.WithSingletonMode(gocron.LimitModeReschedule), gocron.WithStartAt(gocron.WithStartDateTime(startsAt2)), ) if err != nil { logger.Error("failed to start vcenter inventory cron job", "error", err) os.Exit(1) } logger.Debug("Created vcenter inventory cron job", "job", job2.ID(), "starting_at", startsAt2) startsAt3 := time.Now().Add(cronSnapshotFrequency) if cronSnapshotFrequency == time.Hour { startsAt3 = time.Now().Truncate(time.Hour).Add(time.Hour) } job3, err := c.NewJob( gocron.DurationJob(cronSnapshotFrequency), gocron.NewTask(func() { ct.RunVcenterSnapshotHourly(ctx, logger) }), gocron.WithSingletonMode(gocron.LimitModeReschedule), gocron.WithStartAt(gocron.WithStartDateTime(startsAt3)), ) if err != nil { logger.Error("failed to start vcenter inventory snapshot cron job", "error", err) os.Exit(1) } logger.Debug("Created vcenter inventory snapshot cron job", "job", job3.ID(), "starting_at", startsAt3) startsAt4 := time.Now().Add(cronAggregateFrequency) if cronAggregateFrequency == time.Hour*24 { now := time.Now() startsAt4 = time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()) } job4, err := c.NewJob( gocron.DurationJob(cronAggregateFrequency), gocron.NewTask(func() { ct.RunVcenterDailyAggregate(ctx, logger) }), gocron.WithSingletonMode(gocron.LimitModeReschedule), gocron.WithStartAt(gocron.WithStartDateTime(startsAt4)), ) if err != nil { logger.Error("failed to start vcenter inventory aggregation cron job", "error", err) os.Exit(1) } logger.Debug("Created vcenter inventory aggregation cron job", "job", job4.ID(), "starting_at", startsAt4) job5, err := c.NewJob( gocron.CronJob("0 0 1 * *", false), gocron.NewTask(func() { ct.RunVcenterMonthlyAggregate(ctx, logger) }), gocron.WithSingletonMode(gocron.LimitModeReschedule), ) if err != nil { logger.Error("failed to start vcenter monthly aggregation cron job", "error", err) os.Exit(1) } logger.Debug("Created vcenter monthly aggregation cron job", "job", job5.ID()) job6, err := c.NewJob( gocron.CronJob("0 30 2 * *", false), gocron.NewTask(func() { ct.RunSnapshotCleanup(ctx, logger) }), gocron.WithSingletonMode(gocron.LimitModeReschedule), ) if err != nil { logger.Error("failed to start snapshot cleanup cron job", "error", err) os.Exit(1) } logger.Debug("Created snapshot cleanup cron job", "job", job6.ID()) // start cron scheduler c.Start() // Start server r := router.New(logger, database, buildTime, sha1ver, runtime.Version(), &creds, a, s) svr := server.New( logger, c, cancel, bindAddress, server.WithRouter(r), server.SetTls(bindDisableTls), server.SetCertificate(tlsCertFilename), server.SetPrivateKey(tlsKeyFilename), ) //logger.Debug("Server configured", "object", svr) svr.StartAndWait() os.Exit(0) }