Files
vctp/cmd/api/main.go
2024-09-11 21:24:34 +10:00

159 lines
4.6 KiB
Go

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)
}