package main import ( "context" "fmt" "net" "net/url" "os" "os/signal" "slices" "strconv" "strings" "go.alanpearce.eu/website/internal/config" "go.alanpearce.eu/website/internal/server" "go.alanpearce.eu/website/internal/website" "go.alanpearce.eu/x/log" "github.com/ardanlabs/conf/v3" "gitlab.com/tozd/go/errors" ) type Options struct { Development bool `conf:"default:false,flag:dev"` DBPath string `conf:"default:site.db"` Redirect bool `conf:"default:true"` Port int `conf:"default:8080,short:p"` TLS bool `conf:"default:false"` TLSPort int `conf:"default:8443"` ListenAddress string `conf:"default:localhost"` Domains string `conf:"default:localhost"` ACMEIssuer string ACMEIssuerCert string } func main() { options := &Options{} help, err := conf.Parse("", options) if err != nil { if errors.Is(err, conf.ErrHelpWanted) { fmt.Println(help) os.Exit(1) } panic("parsing runtime configuration" + err.Error()) } log := log.Configure(!options.Development) cfg, err := config.GetConfig(".", log.Named("config")) if err != nil { log.Error("error reading configuration file", "error", err) } // Domains? webOpts := &website.Options{ DBPath: options.DBPath, Redirect: options.Redirect, Development: options.Development, Config: cfg, } serverOpts := &server.Options{ Development: options.Development, ListenAddress: options.ListenAddress, Port: options.Port, TLS: options.TLS, TLSPort: options.TLSPort, ACMEIssuer: options.ACMEIssuer, ACMEIssuerCert: options.ACMEIssuerCert, Config: cfg, } if options.Development { webOpts.DBPath = ":memory:" cfg.CSP.ScriptSrc = slices.Insert(cfg.CSP.ScriptSrc, 0, "'unsafe-inline'") cfg.CSP.ConnectSrc = slices.Insert(cfg.CSP.ConnectSrc, 0, "'self'") if options.Domains != "" { cfg.Domains = strings.Split(options.Domains, ",") } else { cfg.Domains = []string{options.ListenAddress} } cfg.BaseURL = mkBaseURL(options, cfg) } sv, err := server.New(serverOpts, log.Named("server")) if err != nil { log.Error("could not create server", "error", err) return } website, err := website.New(webOpts, log.Named("website")) if err != nil { log.Error("could not initialise website", "error", err) return } sv.HostApp(website.App) if options.Redirect { sv.HostFallbackApp(website.MakeRedirectorApp()) } ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() go func() { err = sv.Start() if err != nil { // Error starting or closing listener: log.Fatal("error starting server", err) } }() <-ctx.Done() log.Debug("calling stop") <-sv.Stop() log.Debug("done") } func mkBaseURL(options *Options, cfg *config.Config) config.URL { scheme := "http" port := options.Port if options.TLS { scheme = "https" port = options.TLSPort } return config.URL{ URL: &url.URL{ Scheme: scheme, Host: net.JoinHostPort(cfg.Domains[0], strconv.Itoa(port)), }, } }