11package main
22
33import (
4+ "errors"
5+ "fmt"
46 "os"
7+ "os/exec"
58 "path/filepath"
69 "time"
710)
@@ -28,3 +31,34 @@ func shouldRegenerate(certPath, keyPath string, maxAge time.Duration) bool {
2831 cutoff := time .Now ().Add (- maxAge )
2932 return certInfo .ModTime ().Before (cutoff ) || keyInfo .ModTime ().Before (cutoff )
3033}
34+
35+ // ensureCert returns paths to a valid cached cert+key, regenerating via mkcert if needed.
36+ // extraHosts is added to the mkcert SAN list in addition to localhost, 127.0.0.1, ::1.
37+ func ensureCert (extraHosts []string ) (certPath , keyPath string , err error ) {
38+ dir := certCacheDir ()
39+ if err := os .MkdirAll (dir , 0o700 ); err != nil {
40+ return "" , "" , fmt .Errorf ("create cert cache dir: %w" , err )
41+ }
42+ certPath = filepath .Join (dir , "localhost.pem" )
43+ keyPath = filepath .Join (dir , "localhost.key" )
44+
45+ if ! shouldRegenerate (certPath , keyPath , 30 * 24 * time .Hour ) {
46+ return certPath , keyPath , nil
47+ }
48+
49+ if _ , err := exec .LookPath ("mkcert" ); err != nil {
50+ return "" , "" , errors .New (
51+ "mkcert not found on PATH.\n " +
52+ "install it first:\n " +
53+ " brew install mkcert && mkcert -install" ,
54+ )
55+ }
56+
57+ hosts := append ([]string {"localhost" , "127.0.0.1" , "::1" }, extraHosts ... )
58+ args := append ([]string {"-cert-file" , certPath , "-key-file" , keyPath }, hosts ... )
59+ cmd := exec .Command ("mkcert" , args ... )
60+ if out , err := cmd .CombinedOutput (); err != nil {
61+ return "" , "" , fmt .Errorf ("mkcert failed: %w\n %s" , err , string (out ))
62+ }
63+ return certPath , keyPath , nil
64+ }
0 commit comments