package main import ( "context" "crypto/tls" "fmt" "log" "log/slog" "net/http" "os" "os/signal" "time" "vm-ctp/api/router" utils "vm-ctp/internal/utils" "github.com/gorilla/handlers" "github.com/joho/godotenv" ) // For build numbers, from https://blog.kowalczyk.info/article/vEja/embedding-build-number-in-go-executable.html var sha1ver string // sha1 revision used to build the program var buildTime string // when the executable was built var wait time.Duration var bindDisableTls bool func main() { // Load data from environment file envFilename := utils.GetFilePath(".env") err := godotenv.Load(envFilename) if err != nil { panic("Error loading .env file") } // Open connection to logfile // From https://ispycode.com/GO/Logging/Logging-to-multiple-destinations logFile := os.Getenv("LOG_FILE") if logFile == "" { logFile = "./vctp.log" } logfileWriter, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { fmt.Printf("Unable to write logfile '%s' : '%s'\n", logFile, err) os.Exit(1) } h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true}) slog.SetDefault(slog.New(h)) //log.SetOutput(logfileWriter) //log.Printf("vCTP starting execution. Built on %s from sha1 %s. Runtime %s\n", buildTime, sha1ver, runtime.Version()) r := router.New() // Log everything to stdout in Apache Common Log Format var loggedRouter http.Handler loggedRouter = handlers.LoggingHandler(logfileWriter, r) // Set some options for TLS tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, PreferServerCipherSuites: true, InsecureSkipVerify: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, } // 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) slog.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)) { slog.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename) utils.GenerateCerts(tlsCertFilename, tlsKeyFilename) } // Configure the http server srv := &http.Server{ Addr: bindAddress, // Good practice to set timeouts to avoid Slowloris attacks. WriteTimeout: time.Second * 15, ReadTimeout: time.Second * 15, IdleTimeout: time.Second * 60, Handler: loggedRouter, // Pass our instance of gorilla/mux in. TLSConfig: tlsConfig, TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } slog.Info("Started listening on", "bindAddress", bindAddress) // Run our server in a goroutine so that it doesn't block // Also decide whether to start a TLS or plain http server go func() { if bindDisableTls { if err := srv.ListenAndServe(); err != nil { log.Println(err) } } else { if err := srv.ListenAndServeTLS(tlsCertFilename, tlsKeyFilename); err != nil { log.Println(err) } } }() c := make(chan os.Signal, 1) // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. signal.Notify(c, os.Interrupt) // Block until we receive our signal. <-c // Create a deadline to wait for. ctx, cancel := context.WithTimeout(context.Background(), wait) defer cancel() // Doesn't block if no connections, but will otherwise wait // until the timeout deadline. srv.Shutdown(ctx) // Optionally, you could run srv.Shutdown in a goroutine and block on // <-ctx.Done() if your application should wait for other services // to finalize based on context cancellation. slog.Info("shutting down") os.Exit(0) }