package acme import ( "encoding/json" "errors" "fmt" "log" "net/http" "net/mail" "os" "path/filepath" "time" "imuslab.com/zoraxy/mod/utils" ) /* autorenew.go This script handle auto renew */ type AutoRenewConfig struct { Enabled bool //Automatic renew is enabled Email string //Email for acme RenewAll bool //Renew all or selective renew with the slice below FilesToRenew []string //If RenewAll is false, renew these certificate files } type AutoRenewer struct { ConfigFilePath string CertFolder string RenewerConfig *AutoRenewConfig TickerstopChan chan bool } // Create an auto renew agent, require config filepath and auto scan & renew interval (seconds) // Set renew check interval to 0 for auto (1 day) func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64) (*AutoRenewer, error) { if renewCheckInterval == 0 { renewCheckInterval = 86400 //1 day } ticker := time.NewTicker(time.Duration(renewCheckInterval) * time.Second) done := make(chan bool) //Load the config file. If not found, create one if !utils.FileExists(config) { //Create one os.MkdirAll(filepath.Dir(config), 0775) newConfig := AutoRenewConfig{ RenewAll: true, FilesToRenew: []string{}, } js, _ := json.MarshalIndent(newConfig, "", " ") err := os.WriteFile(config, js, 0775) if err != nil { return nil, errors.New("Failed to create acme auto renewer config: " + err.Error()) } } renewerConfig := AutoRenewConfig{} content, err := os.ReadFile(config) if err != nil { return nil, errors.New("Failed to open acme auto renewer config: " + err.Error()) } err = json.Unmarshal(content, &renewerConfig) if err != nil { return nil, errors.New("Malformed acme config file: " + err.Error()) } //Create an Auto renew object thisRenewer := AutoRenewer{ ConfigFilePath: config, CertFolder: certFolder, RenewerConfig: &renewerConfig, TickerstopChan: done, } //Check and renew certificate on startup thisRenewer.CheckAndRenewCertificates() //Start the ticker to check and renew every x seconds go func() { for { select { case <-done: return case <-ticker.C: log.Println("Check and renew certificates in progress") thisRenewer.CheckAndRenewCertificates() } } }() return &thisRenewer, nil } // Handle update auto renew domains // Set opr for different mode of operations // opr = setSelected -> Enter a list of file names (or matching rules) for auto renew // opr = setAuto -> Set to use auto detect certificates and renew func (a *AutoRenewer) HandleSetAutoRenewDomains(w http.ResponseWriter, r *http.Request) { opr, err := utils.GetPara(r, "opr") if err != nil { utils.SendErrorResponse(w, "Operation not set") return } if opr == "setSelected" { files, err := utils.PostPara(r, "domains") if err != nil { utils.SendErrorResponse(w, "Domains is not defined") return } //Parse it int array of string matchingRuleFiles := []string{} err = json.Unmarshal([]byte(files), &matchingRuleFiles) if err != nil { utils.SendErrorResponse(w, err.Error()) return } //Update the configs a.RenewerConfig.RenewAll = false a.RenewerConfig.FilesToRenew = matchingRuleFiles a.saveRenewConfigToFile() utils.SendOK(w) } else if opr == "setAuto" { a.RenewerConfig.RenewAll = true a.saveRenewConfigToFile() utils.SendOK(w) } } // if auto renew all is true (aka auto scan), it will return []string{"*"} func (a *AutoRenewer) HandleLoadAutoRenewDomains(w http.ResponseWriter, r *http.Request) { results := []string{} if a.RenewerConfig.RenewAll { //Auto pick which cert to renew. results = append(results, "*") } else { //Manually set the files to renew results = a.RenewerConfig.FilesToRenew } js, _ := json.Marshal(results) utils.SendJSONResponse(w, string(js)) } func (a *AutoRenewer) HandleRenewNow(w http.ResponseWriter, r *http.Request) { } func (a *AutoRenewer) HandleAutoRenewEnable(w http.ResponseWriter, r *http.Request) { val, err := utils.PostPara(r, "enable") if err != nil { js, _ := json.Marshal(a.RenewerConfig.Enabled) utils.SendJSONResponse(w, string(js)) } else { if val == "true" { //Check if the email is not empty if a.RenewerConfig.Email == "" { utils.SendErrorResponse(w, "Email is not set") return } a.RenewerConfig.Enabled = true a.saveRenewConfigToFile() log.Println("[AutoRenew] ACME auto renew enabled") } else { a.RenewerConfig.Enabled = false a.saveRenewConfigToFile() log.Println("[AutoRenew] ACME auto renew disabled") } } } func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) { email, err := utils.PostPara(r, "set") if err != nil { //Return the current email to user js, _ := json.Marshal(a.RenewerConfig.Email) utils.SendJSONResponse(w, string(js)) } else { //Check if the email is valid _, err := mail.ParseAddress(email) if err != nil { utils.SendErrorResponse(w, err.Error()) return } //Set the new config a.RenewerConfig.Email = email a.saveRenewConfigToFile() } } // Check and renew certificates. This check all the certificates in the // certificate folder and return a list of certs that is renewed in this call // Return string array with length 0 when no cert is expired func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) { certFolder := a.CertFolder files, err := os.ReadDir(certFolder) if err != nil { log.Println("Unable to renew certificates: " + err.Error()) return []string{}, err } fmt.Println("[ACME DEBUG] Cert found: ", files) return []string{}, nil } // Write the current renewer config to file func (a *AutoRenewer) saveRenewConfigToFile() error { js, _ := json.MarshalIndent(a.RenewerConfig, "", " ") return os.WriteFile(a.ConfigFilePath, js, 0775) }