diff --git a/main.go b/main.go index 3aef787..0be5c88 100644 --- a/main.go +++ b/main.go @@ -72,7 +72,7 @@ func extractSubdomain(host, domain string) string { host = host[:idx] } - // Bare domain — no subdomain + // Bare domain — no subdomain (e.g. "nextwks.eu") domainWithDot := "." + domain if host == domain { return "" @@ -86,22 +86,6 @@ func extractSubdomain(host, domain string) string { // --- Handlers --- -func boilerplateHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, ` - - - - NextWorkspace - - -

NextWorkspace

-

The Self-Hosted Workspace for Startups

- -`) -} - func coreAppHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) @@ -118,6 +102,22 @@ func coreAppHandler(w http.ResponseWriter, r *http.Request) { `) } +func unknownSubdomainHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, ` + + + + Not Found + + +

Not Found

+

No application registered for this subdomain.

+ +`) +} + // --- Main --- func main() { @@ -138,19 +138,19 @@ func main() { case "core": handlers[subdomain] = coreAppHandler default: - log.Printf("Warning: unknown app %q for subdomain %q, using boilerplate", appName, subdomain) - handlers[subdomain] = boilerplateHandler + log.Printf("Warning: unknown app %q for subdomain %q", appName, subdomain) + handlers[subdomain] = unknownSubdomainHandler } } - // Main router + // Main router (for HTTPS or non-TLS mode) router := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { subdomain := extractSubdomain(r.Host, cfg.Domain) if handler, ok := handlers[subdomain]; ok { handler(w, r) return } - boilerplateHandler(w, r) + unknownSubdomainHandler(w, r) }) httpAddr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) @@ -164,12 +164,12 @@ func main() { Path: filepath.Join(configDir, "certs"), } - // Collect domains (from proxies.yaml + bare domain) + // Collect domains from proxies.yaml only (no bare domain, no wildcard) + // Each subdomain gets its own certificate independently. var domains []string for subdomain := range proxies.Apps { domains = append(domains, subdomain+"."+cfg.Domain) } - domains = append(domains, cfg.Domain) // Obtain and manage certificates magic := certmagic.NewDefault() @@ -177,19 +177,30 @@ func main() { log.Fatalf("Failed to manage certificates: %v", err) } - // HTTP redirect handler (on :80) - redirectHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - target := "https://" + r.Host + r.URL.RequestURI() - http.Redirect(w, r, target, http.StatusMovedPermanently) + // Extract ACME issuer for http-01 challenge handling + var acmeIssuer *certmagic.ACMEIssuer + if len(magic.Issuers) > 0 { + acmeIssuer, _ = magic.Issuers[0].(*certmagic.ACMEIssuer) + } + + // HTTP handler on :80 + // - Known subdomains → redirect to HTTPS + // - Unknown subdomains → error directly on HTTP (no redirect to broken HTTPS) + // - ACME challenge paths → handled by certmagic before reaching us + var httpHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + subdomain := extractSubdomain(r.Host, cfg.Domain) + if _, ok := handlers[subdomain]; ok { + target := "https://" + r.Host + r.URL.RequestURI() + http.Redirect(w, r, target, http.StatusMovedPermanently) + return + } + unknownSubdomainHandler(w, r) }) // Wrap with ACME challenge handler for Let's Encrypt http-01 validation - var httpHandler http.Handler = redirectHandler - if len(magic.Issuers) > 0 { - if ai, ok := magic.Issuers[0].(*certmagic.ACMEIssuer); ok { - httpHandler = ai.HTTPChallengeHandler(redirectHandler) - log.Println("ACME challenge handler enabled on :80") - } + if acmeIssuer != nil { + httpHandler = acmeIssuer.HTTPChallengeHandler(httpHandler) + log.Println("ACME challenge handler enabled on :80") } httpServer := &http.Server{ @@ -197,7 +208,7 @@ func main() { Handler: httpHandler, } - // HTTPS server with TLS + // HTTPS server on :443 with TLS tlsConfig := magic.TLSConfig() tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") // HTTP/2 support @@ -207,9 +218,9 @@ func main() { TLSConfig: tlsConfig, } - // Start HTTP redirect server + // Start HTTP server go func() { - log.Printf("HTTP redirect listening on %s", httpAddr) + log.Printf("HTTP listening on %s", httpAddr) if err := httpServer.ListenAndServe(); err != nil { log.Fatalf("HTTP server error: %v", err) }