package main import ( "context" "fmt" "log/slog" "os" "runtime" "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 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 database, err := db.New(logger, utils.GetFilePath("db.sqlite3")) 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); 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) // 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()) // start background checks of vcenter inventory startsAt2 := time.Now().Add(time.Second * 30) 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()) // 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) }