package main import ( "crypto/tls" "flag" "html/template" "log" "net/http" "os" "path/filepath" "golang.org/x/crypto/acme/autocert" ) var ( dev = flag.Bool("dev", false, "Development mode (HTTP on port 8080)") domain = flag.String("domain", "inou.com", "Domain for TLS certificate") certDir = flag.String("certs", "/tank/inou/certs", "Certificate cache directory") tmplDir = flag.String("templates", "templates", "Templates directory") staticDir = flag.String("static", "static", "Static files directory") ) var templates *template.Template func main() { flag.Parse() loadTemplates() mux := http.NewServeMux() mux.HandleFunc("/", handleLanding) mux.HandleFunc("/login", handleLogin) mux.HandleFunc("/dashboard", handleDashboard) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(*staticDir)))) handler := securityHeaders(mux) if *dev { log.Println("Development mode: http://localhost:8080") log.Fatal(http.ListenAndServe(":8080", handler)) } else { go redirectHTTP() serveTLS(handler) } } func loadTemplates() { pattern := filepath.Join(*tmplDir, "*.tmpl") var err error templates, err = template.ParseGlob(pattern) if err != nil { log.Fatalf("Failed to load templates: %v", err) } log.Printf("Loaded templates: %s", pattern) } func render(w http.ResponseWriter, name string, data any) { w.Header().Set("Content-Type", "text/html; charset=utf-8") err := templates.ExecuteTemplate(w, name, data) if err != nil { log.Printf("Template error: %v", err) http.Error(w, "Internal error", http.StatusInternalServerError) } } func securityHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-XSS-Protection", "1; mode=block") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") w.Header().Del("Server") next.ServeHTTP(w, r) }) } func redirectHTTP() { srv := &http.Server{ Addr: ":80", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { target := "https://" + r.Host + r.URL.Path if r.URL.RawQuery != "" { target += "?" + r.URL.RawQuery } http.Redirect(w, r, target, http.StatusMovedPermanently) }), } log.Println("HTTP redirect: :80 → :443") if err := srv.ListenAndServe(); err != nil { log.Printf("HTTP redirect server error: %v", err) } } func serveTLS(handler http.Handler) { if err := os.MkdirAll(*certDir, 0700); err != nil { log.Fatalf("Failed to create cert directory: %v", err) } m := &autocert.Manager{ Cache: autocert.DirCache(*certDir), Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(*domain), } srv := &http.Server{ Addr: ":443", Handler: handler, TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, } log.Printf("HTTPS server: https://%s", *domain) log.Fatal(srv.ListenAndServeTLS("", "")) } // Handlers func handleLanding(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } render(w, "landing.tmpl", nil) } func handleLogin(w http.ResponseWriter, r *http.Request) { render(w, "login.tmpl", nil) } func handleDashboard(w http.ResponseWriter, r *http.Request) { data := map[string]any{ "Email": "johan@jongsma.me", } render(w, "dashboard.tmpl", data) }