Browse Source

auto update script executed

Toby Chui 1 year ago
parent
commit
b9c1cec216
6 changed files with 229 additions and 30 deletions
  1. 1 0
      api.go
  2. 17 0
      cert.go
  3. 100 12
      mod/acme/autorenew.go
  4. 2 1
      mod/pathrule/pathrule.go
  5. 5 4
      start.go
  6. 104 13
      web/snippet/acme.html

+ 1 - 0
api.go

@@ -155,6 +155,7 @@ func initAPIs() {
 	//ACME & Auto Renewer
 	//ACME & Auto Renewer
 	authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
 	authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
 	authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
 	authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
+	authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
 	authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
 	authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
 	authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
 	authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
 	authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
 	authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)

+ 17 - 0
cert.go

@@ -145,6 +145,23 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}
 	}
 
 
+	requireCompact, _ := utils.GetPara(r, "compact")
+	if requireCompact == "true" {
+		result := make(map[string][]string)
+
+		for key, value := range certnameToDomainMap {
+			if _, ok := result[value]; !ok {
+				result[value] = make([]string, 0)
+			}
+
+			result[value] = append(result[value], key)
+		}
+
+		js, _ := json.Marshal(result)
+		utils.SendJSONResponse(w, string(js))
+		return
+	}
+
 	js, _ := json.Marshal(certnameToDomainMap)
 	js, _ := json.Marshal(certnameToDomainMap)
 	utils.SendJSONResponse(w, string(js))
 	utils.SendJSONResponse(w, string(js))
 }
 }

+ 100 - 12
mod/acme/autorenew.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"fmt"
 	"log"
 	"log"
 	"net/http"
 	"net/http"
+	"net/mail"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"time"
 	"time"
@@ -20,10 +21,10 @@ import (
 */
 */
 
 
 type AutoRenewConfig struct {
 type AutoRenewConfig struct {
-	Email          string //Email for acme
-	AutomaticRenew bool   //Automatic renew is enabled
-	RenewAll       bool   //Renew all or selective renew with the slice below
-	DomainsToRenew []string
+	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 {
 type AutoRenewer struct {
@@ -48,8 +49,8 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64)
 		//Create one
 		//Create one
 		os.MkdirAll(filepath.Dir(config), 0775)
 		os.MkdirAll(filepath.Dir(config), 0775)
 		newConfig := AutoRenewConfig{
 		newConfig := AutoRenewConfig{
-			RenewAll:       true,
-			DomainsToRenew: []string{},
+			RenewAll:     true,
+			FilesToRenew: []string{},
 		}
 		}
 		js, _ := json.MarshalIndent(newConfig, "", " ")
 		js, _ := json.MarshalIndent(newConfig, "", " ")
 		err := os.WriteFile(config, js, 0775)
 		err := os.WriteFile(config, js, 0775)
@@ -96,27 +97,108 @@ func NewAutoRenewer(config string, certFolder string, renewCheckInterval int64)
 	return &thisRenewer, nil
 	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) {
 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) {
 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) HandleRenewNow(w http.ResponseWriter, r *http.Request) {
 
 
 }
 }
 
 
-func (a *AutoRenewer) HandleACMEEmail(w http.ResponseWriter, r *http.Request) {
-	/*
-		email, err := utils.PostPara(r, "set")
-		if err != nil {
-			currentEmail := ""
+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 {
 		} 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
 // Check and renew certificates. This check all the certificates in the
@@ -133,3 +215,9 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
 	fmt.Println("[ACME DEBUG] Cert found: ", files)
 	fmt.Println("[ACME DEBUG] Cert found: ", files)
 	return []string{}, nil
 	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)
+}

+ 2 - 1
mod/pathrule/pathrule.go

@@ -19,6 +19,7 @@ import (
 */
 */
 
 
 type Options struct {
 type Options struct {
+	Enabled      bool   //If the pathrule is enabled.
 	ConfigFolder string //The folder to store the path blocking config files
 	ConfigFolder string //The folder to store the path blocking config files
 }
 }
 
 
@@ -39,7 +40,7 @@ type Handler struct {
 }
 }
 
 
 // Create a new path blocker handler
 // Create a new path blocker handler
-func NewPathBlocker(options *Options) *Handler {
+func NewPathRuleHandler(options *Options) *Handler {
 	//Create folder if not exists
 	//Create folder if not exists
 	if !utils.FileExists(options.ConfigFolder) {
 	if !utils.FileExists(options.ConfigFolder) {
 		os.Mkdir(options.ConfigFolder, 0775)
 		os.Mkdir(options.ConfigFolder, 0775)

+ 5 - 4
start.go

@@ -96,13 +96,14 @@ func startupSequence() {
 	}
 	}
 
 
 	/*
 	/*
-		Path Blocker
+		Path Rules
 
 
-		This section of starutp script start the pathblocker
-		from file.
+		This section of starutp script start the path rules where
+		user can define their own routing logics
 	*/
 	*/
 
 
-	pathRuleHandler = pathrule.NewPathBlocker(&pathrule.Options{
+	pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{
+		Enabled:      false,
 		ConfigFolder: "./rules/pathrules",
 		ConfigFolder: "./rules/pathrules",
 	})
 	})
 
 

+ 104 - 13
web/snippet/acme.html

@@ -31,19 +31,21 @@
         </div>
         </div>
     </div>
     </div>
     <div class="ui basic segment">
     <div class="ui basic segment">
+      <p style="float: right; color: #21ba45; display:none;" id="enableToggleSucc"><i class="green checkmark icon"></i> Setting Updated</p>
       <div class="ui toggle checkbox">
       <div class="ui toggle checkbox">
         <input type="checkbox" id="enableCertAutoRenew">
         <input type="checkbox" id="enableCertAutoRenew">
         <label>Enable Certificate Auto Renew</label>
         <label>Enable Certificate Auto Renew</label>
       </div>
       </div>
       <br>
       <br>
       <h3>ACME Email</h3>
       <h3>ACME Email</h3>
-      <p>Email is generally required for renewing via ACME. Zoraxy do not support no-email renew due to security reasons.</p>
+      <p>Email is required by many CAs for renewing via ACME protocol</p>
       <div class="ui fluid action input">
       <div class="ui fluid action input">
         <input id="caRegisterEmail" type="text" placeholder="[email protected]">
         <input id="caRegisterEmail" type="text" placeholder="[email protected]">
-        <button class="ui icon basic button">
+        <button class="ui icon basic button" onclick="saveEmailToConfig();">
             <i class="blue save icon"></i>
             <i class="blue save icon"></i>
         </button>
         </button>
       </div>
       </div>
+      <small>If you don't want to share your private email address, you can also fill in an email address that point to a mailbox not exists on your domain.</small>
     </div>
     </div>
     <div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
     <div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
       <div class="ui accordion advanceSettings">
       <div class="ui accordion advanceSettings">
@@ -57,14 +59,14 @@
                 <input type="checkbox" id="renewAllSupported" onchange="setAutoRenewIfCASupportMode(this.checked);" checked>
                 <input type="checkbox" id="renewAllSupported" onchange="setAutoRenewIfCASupportMode(this.checked);" checked>
                 <label>Auto renew if CA is supported</label>
                 <label>Auto renew if CA is supported</label>
               </div><br>
               </div><br>
-              <button class="ui basic right floated button" style="margin-top: -2em;"><i class="yellow refresh icon"></i> Renew Now</button>
+              <button id="renewNowBtn" onclick="renewNow();" class="ui basic right floated button" style="margin-top: -2em;"><i class="yellow refresh icon"></i> Renew Now</button>
               <div class="ui horizontal divider"> OR </div>
               <div class="ui horizontal divider"> OR </div>
               <p>Select the certificates to automatic renew in the list below</p>
               <p>Select the certificates to automatic renew in the list below</p>
               <table id="domainCertFileTable" class="ui very compact unstackable basic disabled table">
               <table id="domainCertFileTable" class="ui very compact unstackable basic disabled table">
                 <thead>
                 <thead>
                   <tr>
                   <tr>
                     <th>Domain Name</th>
                     <th>Domain Name</th>
-                    <th>Filename</th>
+                    <th>Match Rule</th>
                     <th>Auto-Renew</th>
                     <th>Auto-Renew</th>
                   </tr>
                   </tr>
                 </thead>
                 </thead>
@@ -75,7 +77,7 @@
                 Certificate Renew only works on the certification authority (CA) supported by Zoraxy. Check Zoraxy wiki for more information on supported list of CAs.
                 Certificate Renew only works on the certification authority (CA) supported by Zoraxy. Check Zoraxy wiki for more information on supported list of CAs.
               </div>
               </div>
               <button class="ui basic right floated button"><i class="blue save icon"></i> Save Changes</button>
               <button class="ui basic right floated button"><i class="blue save icon"></i> Save Changes</button>
-              <button class="ui basic right floated button"><i class="yellow refresh icon"></i> Renew Selected</button>
+              <button id="renewSelectedButton" onclick="renewNow();" class="ui basic right floated disabled button"><i class="yellow refresh icon"></i> Renew Selected</button>
               <br><br>
               <br><br>
           </div>
           </div>
       </div>
       </div>
@@ -126,13 +128,61 @@
     function setAutoRenewIfCASupportMode(useAutoMode = true){
     function setAutoRenewIfCASupportMode(useAutoMode = true){
       if (useAutoMode){
       if (useAutoMode){
         $("#domainCertFileTable").addClass("disabled");
         $("#domainCertFileTable").addClass("disabled");
+        $("#renewNowBtn").removeClass("disabled");
+        $("#renewSelectedButton").addClass("disabled");
       }else{
       }else{
         $("#domainCertFileTable").removeClass("disabled");
         $("#domainCertFileTable").removeClass("disabled");
+        $("#renewNowBtn").addClass("disabled");
+        $("#renewSelectedButton").removeClass("disabled");
       }
       }
     }
     }
 
 
+    function initRenewerConfigFromFile(){
+      $.get("/api/acme/autoRenew/enable", function(data){
+        if (data == true){
+          $("#enableCertAutoRenew").parent().checkbox("set checked");
+        }
+
+        $("#enableCertAutoRenew").on("change", function(){
+          toggleAutoRenew();
+        })
+      });
+
+      $.get("/api/acme/autoRenew/email", function(data){
+        if (data != "" && data != undefined && data != null){
+          $("#caRegisterEmail").val(data);
+        }
+      });
+    }
+    initRenewerConfigFromFile();
+
+    function saveEmailToConfig(){
+      $.ajax({
+        url: "/api/acme/autoRenew/email",
+        data: {set: $("#caRegisterEmail").val()},
+        success: function(data){
+          if (data.error != undefined){
+            parent.msgbox(data.error, false, 5000);
+          }else{
+            parent.msgbox("Email updated");
+          }
+        }
+      });
+    }
+
+    function toggleAutoRenew(){
+      var enabled = $("#enableCertAutoRenew").parent().checkbox("is checked");
+      $.post("/api/acme/autoRenew/enable?enable=" + enabled, function(data){
+        if (data.error){
+          parent.msgbox(data.error, false, 5000);
+        }else{
+          $("#enableToggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
+        }
+      });
+    }
+
     //Render the domains table that exists in this zoraxy host
     //Render the domains table that exists in this zoraxy host
-    function renderDomainTable(domains) {
+    function renderDomainTable(domainFileList) {
       // Get the table body element
       // Get the table body element
       var tableBody = $('#domainTableBody');
       var tableBody = $('#domainTableBody');
       
       
@@ -141,25 +191,30 @@
       
       
       // Iterate over the domain names
       // Iterate over the domain names
       var counter = 0;
       var counter = 0;
-      for (const [domain, srcfile] of Object.entries(domains)) {
+      for (const [srcfile, domains] of Object.entries(domainFileList)) {
 
 
         // Create a table row
         // Create a table row
         var row = $('<tr>');
         var row = $('<tr>');
         
         
         // Create the domain name cell
         // Create the domain name cell
         var domainClass = "validDomain";
         var domainClass = "validDomain";
-        if (expiredDomains.includes(domain)){
-          domainClass = "expiredDomain";
+        for (var i = 0; i < domains.length; i++){
+          let thisDomain = domains[i];
+          if (expiredDomains.includes(thisDomain)){
+            domainClass = "expiredDomain";
+          }
         }
         }
-        var domainCell = $('<td class="' + domainClass  +'">').text(domain);
+       
+        var domainCell = $('<td class="' + domainClass  +'">').html(domains.join("<br>"));
         row.append(domainCell);
         row.append(domainCell);
 
 
         var srcFileCell = $('<td>').text(srcfile);
         var srcFileCell = $('<td>').text(srcfile);
         row.append(srcFileCell);
         row.append(srcFileCell);
         
         
         // Create the auto-renew checkbox cell
         // Create the auto-renew checkbox cell
-        var checkboxCell = $(`<td domain="${domain}" srcfile="${srcfile}">`);
-        var checkbox = $('<input>').attr('type', 'checkbox');
+        let domainsEncoded = encodeURIComponent(JSON.stringify(domains));
+        var checkboxCell = $(`<td domain="${domainsEncoded}" srcfile="${srcfile}">`);
+        var checkbox = $(`<input name="${srcfile}">`).attr('type', 'checkbox');
         checkboxCell.append(checkbox);
         checkboxCell.append(checkbox);
         row.append(checkboxCell);
         row.append(checkboxCell);
         
         
@@ -173,12 +228,13 @@
     //Initiate domain table. If you needs to update the expired domain as well
     //Initiate domain table. If you needs to update the expired domain as well
     //call from initDomainFileList() instead
     //call from initDomainFileList() instead
     function initDomainTable(){
     function initDomainTable(){
-      $.get("/api/cert/listdomains", function(data){
+      $.get("/api/cert/listdomains?compact=true", function(data){
         if (data.error != undefined){
         if (data.error != undefined){
           parent.msgbox(data.error, false);
           parent.msgbox(data.error, false);
         }else{
         }else{
           renderDomainTable(data);
           renderDomainTable(data);
         }
         }
+        initAutoRenewPolicy();
       })
       })
     }
     }
 
 
@@ -261,6 +317,8 @@
       }
       }
     }
     }
 
 
+    //Grab the longest common suffix of all domains
+    //not that smart technically
     function autoDetectMatchingRules(){
     function autoDetectMatchingRules(){
       var domainsString = $("#domainsInput").val();
       var domainsString = $("#domainsInput").val();
       if (!domainsString.includes(",")){
       if (!domainsString.includes(",")){
@@ -312,6 +370,39 @@
       $("#filenameInput").val(longestSuffix);
       $("#filenameInput").val(longestSuffix);
     }
     }
 
 
+    //Handle the renew now btn click
+    function renewNow(){
+      alert("wip");
+      return
+      $.get("/api/acme/autoRenew/renewNow", function(data){
+        alert(data);
+      })
+    }
+
+    function initAutoRenewPolicy(){
+      $.get("/api/acme/autoRenew/listDomains", function(data){
+        if (data.error != undefined){
+          parent.msgbox(data.error, false)
+        }else{
+          if (data[0] == "*"){
+            //Auto select and renew is enabled
+            $("#renewAllSupported").parent().checkbox("set checked");
+          }else{
+            //This is a list of domain files
+
+          }
+        }
+      })
+    }
+
+    function saveAutoRenewPolicy(){
+      let autoRenewAll = $("#renewAllSupported").parent().checkbox("is checked");
+      if (autoRenewAll == true){
+
+      }else{
+
+      }
+    }
 
 
     //Clear  up the input field when page load
     //Clear  up the input field when page load
     $("#filenameInput").val("");
     $("#filenameInput").val("");