From 672653f73139b23ae92dc52613c2e5d3d06910a0 Mon Sep 17 00:00:00 2001 From: cclohmar Date: Mon, 6 Jul 2026 15:45:23 +0100 Subject: [PATCH] feat: deploy modes (--destroy) + HTTPS with Let's Encrypt --- configs/core/config.yaml | 4 +-- main.go | 53 ++++++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/configs/core/config.yaml b/configs/core/config.yaml index b6d5585..5bc7d7e 100644 --- a/configs/core/config.yaml +++ b/configs/core/config.yaml @@ -3,7 +3,7 @@ server: host: "0.0.0.0" tls: - enabled: false # Flip to true for Let's Encrypt HTTPS - email: "" # Required when enabled (e.g. admin@nextwks.eu) + enabled: true # ← was false, now true + email: "den@2-4-h.net" # ← Let's Encrypt account email domain: "nextwks.eu" # Base domain for subdomain routing + certs diff --git a/main.go b/main.go index 15d5cb3..3aef787 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "crypto/tls" "fmt" "log" "net/http" @@ -154,7 +153,7 @@ func main() { boilerplateHandler(w, r) }) - addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) + httpAddr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) if cfg.TLS.Enabled { log.Println("NextWorkspace starting with TLS (certmagic)...") @@ -165,27 +164,61 @@ func main() { Path: filepath.Join(configDir, "certs"), } + // Collect domains (from proxies.yaml + bare domain) 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() if err := magic.ManageSync(context.Background(), domains); err != nil { log.Fatalf("Failed to manage certificates: %v", err) } - tlsConfig := magic.TLSConfig() - listener, err := tls.Listen("tcp", addr, tlsConfig) - if err != nil { - log.Fatalf("Failed to start TLS listener: %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) + }) + + // 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") + } } - log.Printf("NextWorkspace listening on %s (TLS)", addr) - log.Fatal(http.Serve(listener, router)) + httpServer := &http.Server{ + Addr: httpAddr, + Handler: httpHandler, + } + + // HTTPS server with TLS + tlsConfig := magic.TLSConfig() + tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") // HTTP/2 support + + httpsServer := &http.Server{ + Addr: ":443", + Handler: router, + TLSConfig: tlsConfig, + } + + // Start HTTP redirect server + go func() { + log.Printf("HTTP redirect listening on %s", httpAddr) + if err := httpServer.ListenAndServe(); err != nil { + log.Fatalf("HTTP server error: %v", err) + } + }() + + log.Println("HTTPS listening on :443") + log.Fatal(httpsServer.ListenAndServeTLS("", "")) // certmagic provides certs via TLSConfig } else { - log.Printf("NextWorkspace listening on %s (no TLS)", addr) - log.Fatal(http.ListenAndServe(addr, router)) + log.Printf("NextWorkspace listening on %s (no TLS)", httpAddr) + log.Fatal(http.ListenAndServe(httpAddr, router)) } }