package main import ( "context" "flag" "fmt" "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" ) 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() { settingsPath := flag.String("settings", "/etc/dtms/vctp.yml", "Path to settings YAML") flag.Parse() bootstrapLogger := log.New(log.LevelInfo, log.OutputText) ctx, cancel := context.WithCancel(context.Background()) // Load settings from yaml s := settings.New(bootstrapLogger, *settingsPath) err := s.ReadYMLSettings() if err != nil { bootstrapLogger.Error("failed to open yaml settings file", "error", err, "filename", *settingsPath) os.Exit(1) } logger := log.New( log.ToLevel(strings.ToLower(strings.TrimSpace(s.Values.Settings.LogLevel))), log.ToOutput(strings.ToLower(strings.TrimSpace(s.Values.Settings.LogOutput))), ) s.Logger = logger // Configure database dbDriver := strings.TrimSpace(s.Values.Settings.DatabaseDriver) if dbDriver == "" { dbDriver = "sqlite" } normalizedDriver := strings.ToLower(strings.TrimSpace(dbDriver)) if normalizedDriver == "" || normalizedDriver == "sqlite3" { normalizedDriver = "sqlite" } dbURL := strings.TrimSpace(s.Values.Settings.DatabaseURL) if dbURL == "" && normalizedDriver == "sqlite" { dbURL = utils.GetFilePath("db.sqlite3") } database, err := db.New(logger, db.Config{Driver: normalizedDriver, 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, normalizedDriver); err != nil { logger.Error("failed to migrate database", "error", err) os.Exit(1) } // Determine bind IP bindIP := strings.TrimSpace(s.Values.Settings.BindIP) if bindIP == "" { bindIP = utils.GetOutboundIP().String() } // Determine bind port bindPort := s.Values.Settings.BindPort if bindPort == 0 { bindPort = 9443 } bindAddress := fmt.Sprint(bindIP, ":", bindPort) //logger.Info("Will listen on address", "ip", bindIP, "port", bindPort) // Determine bind disable TLS bindDisableTls = s.Values.Settings.BindDisableTLS // Get file names for TLS cert/key tlsCertFilename := strings.TrimSpace(s.Values.Settings.TLSCertFilename) if tlsCertFilename != "" { tlsCertFilename = utils.GetFilePath(tlsCertFilename) } else { tlsCertFilename = "./cert.pem" } tlsKeyFilename := strings.TrimSpace(s.Values.Settings.TLSKeyFilename) 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 serttings, decrypt if required a := secrets.New(logger, encryptionKey) vcEp := strings.TrimSpace(s.Values.Settings.VcenterPassword) 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) if cipherText, encErr := a.Encrypt([]byte(vcEp)); encErr != nil { logger.Warn("failed to encrypt vcenter credentials", "error", encErr) } else { s.Values.Settings.VcenterPassword = cipherText if err := s.WriteYMLSettings(); err != nil { logger.Warn("failed to update settings with encrypted vcenter password", "error", err) } else { logger.Info("encrypted vcenter password stored in settings file") } } //os.Exit(1) } creds := vcenter.VcenterLogin{ Username: strings.TrimSpace(s.Values.Settings.VcenterUsername), Password: string(vcPass), Insecure: s.Values.Settings.VcenterInsecure, } if creds.Username == "" { logger.Error("No vcenter username configured") os.Exit(1) } // 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, } /* cronFrequency = durationFromSeconds(s.Values.Settings.VcenterEventPollingSeconds, 60) logger.Debug("Setting VM event polling cronjob frequency to", "frequency", cronFrequency) cronInvFrequency = durationFromSeconds(s.Values.Settings.VcenterInventoryPollingSeconds, 7200) logger.Debug("Setting VM inventory polling cronjob frequency to", "frequency", cronInvFrequency) */ cronSnapshotFrequency = durationFromSeconds(s.Values.Settings.VcenterInventorySnapshotSeconds, 3600) logger.Debug("Setting VM inventory snapshot cronjob frequency to", "frequency", cronSnapshotFrequency) cronAggregateFrequency = durationFromSeconds(s.Values.Settings.VcenterInventoryAggregateSeconds, 86400) logger.Debug("Setting VM inventory daily 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) monthlyCron := "0 0 1 * *" logger.Debug("Setting monthly aggregation cron schedule", "cron", monthlyCron) job5, err := c.NewJob( gocron.CronJob(monthlyCron, 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()) snapshotCleanupCron := strings.TrimSpace(s.Values.Settings.SnapshotCleanupCron) if snapshotCleanupCron == "" { snapshotCleanupCron = "30 2 * * *" } job6, err := c.NewJob( gocron.CronJob(snapshotCleanupCron, 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) } func durationFromSeconds(value int, fallback int) time.Duration { if value <= 0 { return time.Second * time.Duration(fallback) } return time.Second * time.Duration(value) }