From 153d056bdf4d40b30a84c54bbe8950a71337fae2 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Wed, 12 Jul 2023 21:42:09 +0800 Subject: [PATCH] ACME compatibility fix for /.well-known/ + Updated acme well known take-over regrex + Added experimental config export and import + Added unit test for location rewrite in dpcore + Moved all config files to ./conf and original proxy files to ./conf/proxy + Minor optimization on UI regarding TLS verification logo on subdomain and vdir list --- src/acme.go | 2 +- src/api.go | 8 +- src/cert.go | 8 +- src/config.go | 211 +++++++++++++++++++- src/main.go | 43 ++-- src/mod/acme/acme.go | 8 +- src/mod/acme/autorenew.go | 6 + src/mod/dynamicproxy/dpcore/dpcore.go | 11 +- src/mod/dynamicproxy/dpcore/dpcore_test.go | 49 +++++ src/mod/dynamicproxy/dpcore/utils.go | 5 + src/mod/dynamicproxy/redirection/handler.go | 9 +- src/mod/ganserv/authkeyLinux.go | 4 +- src/mod/ganserv/authkeyWin.go | 10 +- src/reverseproxy.go | 2 +- src/start.go | 11 +- src/web/components/subd.html | 9 +- src/web/components/utils.html | 6 +- src/web/components/vdir.html | 10 +- src/web/snippet/configTools.html | 100 ++++++++++ 19 files changed, 455 insertions(+), 57 deletions(-) create mode 100644 src/mod/dynamicproxy/dpcore/dpcore_test.go create mode 100644 src/web/snippet/configTools.html diff --git a/src/acme.go b/src/acme.go index 5699bdf..dc44039 100644 --- a/src/acme.go +++ b/src/acme.go @@ -48,7 +48,7 @@ func acmeRegisterSpecialRoutingRule() { err := dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{ ID: "acme-autorenew", MatchRule: func(r *http.Request) bool { - found, _ := regexp.MatchString("/.well-known/*", r.RequestURI) + found, _ := regexp.MatchString("/.well-known/acme-challenge/*", r.RequestURI) return found }, RoutingHandler: func(w http.ResponseWriter, r *http.Request) { diff --git a/src/api.go b/src/api.go index 9fc95d0..77ba527 100644 --- a/src/api.go +++ b/src/api.go @@ -150,9 +150,6 @@ func initAPIs() { http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail) http.HandleFunc("/api/account/new", HandleNewPasswordSetup) - //Others - http.HandleFunc("/api/info/x", HandleZoraxyInfo) - //ACME & Auto Renewer authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains) authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate) @@ -164,6 +161,11 @@ func initAPIs() { authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow) authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard + //Others + http.HandleFunc("/api/info/x", HandleZoraxyInfo) + http.HandleFunc("/api/conf/export", ExportConfigAsZip) + http.HandleFunc("/api/conf/import", ImportConfigFromZip) + //If you got APIs to add, append them here } diff --git a/src/cert.go b/src/cert.go index c64268a..e0f3464 100644 --- a/src/cert.go +++ b/src/cert.go @@ -111,7 +111,7 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) { // List all certificates and map all their domains to the cert filename func handleListDomains(w http.ResponseWriter, r *http.Request) { - filenames, err := os.ReadDir("./certs/") + filenames, err := os.ReadDir("./conf/certs/") if err != nil { utils.SendErrorResponse(w, err.Error()) @@ -123,7 +123,7 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) { if filename.IsDir() { continue } - certFilepath := filepath.Join("./certs/", filename.Name()) + certFilepath := filepath.Join("./conf/certs/", filename.Name()) certBtyes, err := os.ReadFile(certFilepath) if err != nil { @@ -273,8 +273,8 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) { defer file.Close() // create file in upload directory - os.MkdirAll("./certs", 0775) - f, err := os.Create(filepath.Join("./certs", overWriteFilename)) + os.MkdirAll("./conf/certs", 0775) + f, err := os.Create(filepath.Join("./conf/certs", overWriteFilename)) if err != nil { http.Error(w, "Failed to create file", http.StatusInternalServerError) return diff --git a/src/config.go b/src/config.go index e4c7da8..4fce2ef 100644 --- a/src/config.go +++ b/src/config.go @@ -1,12 +1,18 @@ package main import ( + "archive/zip" "encoding/json" + "fmt" + "io" "io/ioutil" "log" + "net/http" "os" "path/filepath" + "strconv" "strings" + "time" "imuslab.com/zoraxy/mod/dynamicproxy" "imuslab.com/zoraxy/mod/utils" @@ -31,7 +37,7 @@ type Record struct { func SaveReverseProxyConfig(proxyConfigRecord *Record) error { //TODO: Make this accept new def types - os.MkdirAll("conf", 0775) + os.MkdirAll("./conf/proxy/", 0775) filename := getFilenameFromRootName(proxyConfigRecord.Rootname) //Generate record @@ -39,12 +45,12 @@ func SaveReverseProxyConfig(proxyConfigRecord *Record) error { //Write to file js, _ := json.MarshalIndent(thisRecord, "", " ") - return ioutil.WriteFile(filepath.Join("conf", filename), js, 0775) + return ioutil.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775) } func RemoveReverseProxyConfig(rootname string) error { filename := getFilenameFromRootName(rootname) - removePendingFile := strings.ReplaceAll(filepath.Join("conf", filename), "\\", "/") + removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/") log.Println("Config Removed: ", removePendingFile) if utils.FileExists(removePendingFile) { err := os.Remove(removePendingFile) @@ -83,3 +89,202 @@ func getFilenameFromRootName(rootname string) string { filename = filename + ".config" return filename } + +/* + Importer and Exporter of Zoraxy proxy config +*/ + +func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) { + includeSysDBRaw, err := utils.GetPara(r, "includeDB") + includeSysDB := false + if includeSysDBRaw == "true" { + //Include the system database in backup snapshot + //Temporary set it to read only + sysdb.ReadOnly = true + includeSysDB = true + } + + // Specify the folder path to be zipped + folderPath := "./conf/" + + // Set the Content-Type header to indicate it's a zip file + w.Header().Set("Content-Type", "application/zip") + // Set the Content-Disposition header to specify the file name + w.Header().Set("Content-Disposition", "attachment; filename=\"config.zip\"") + + // Create a zip writer + zipWriter := zip.NewWriter(w) + defer zipWriter.Close() + + // Walk through the folder and add files to the zip + err = filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error { + if err != nil { + return err + } + + if folderPath == filePath { + //Skip root folder + return nil + } + + // Create a new file in the zip + if !utils.IsDir(filePath) { + zipFile, err := zipWriter.Create(filePath) + if err != nil { + return err + } + + // Open the file on disk + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + // Copy the file contents to the zip file + _, err = io.Copy(zipFile, file) + if err != nil { + return err + } + } + + return nil + }) + + if includeSysDB { + //Also zip in the sysdb + zipFile, err := zipWriter.Create("sys.db") + if err != nil { + log.Println("[Backup] Unable to zip sysdb: " + err.Error()) + return + } + + // Open the file on disk + file, err := os.Open("sys.db") + if err != nil { + log.Println("[Backup] Unable to open sysdb: " + err.Error()) + return + } + defer file.Close() + + // Copy the file contents to the zip file + _, err = io.Copy(zipFile, file) + if err != nil { + log.Println(err) + return + } + + //Restore sysdb state + sysdb.ReadOnly = false + } + + if err != nil { + // Handle the error and send an HTTP response with the error message + http.Error(w, fmt.Sprintf("Failed to zip folder: %v", err), http.StatusInternalServerError) + return + } +} + +func ImportConfigFromZip(w http.ResponseWriter, r *http.Request) { + // Check if the request is a POST with a file upload + if r.Method != http.MethodPost { + http.Error(w, "Invalid request method", http.StatusBadRequest) + return + } + + // Max file size limit (10 MB in this example) + r.ParseMultipartForm(10 << 20) + + // Get the uploaded file + file, handler, err := r.FormFile("file") + if err != nil { + http.Error(w, "Failed to retrieve uploaded file", http.StatusInternalServerError) + return + } + defer file.Close() + + if filepath.Ext(handler.Filename) != ".zip" { + http.Error(w, "Upload file is not a zip file", http.StatusInternalServerError) + return + } + // Create the target directory to unzip the files + targetDir := "./conf" + if utils.FileExists(targetDir) { + //Backup the old config to old + os.Rename("./conf", "./conf.old_"+strconv.Itoa(int(time.Now().Unix()))) + } + + err = os.MkdirAll(targetDir, os.ModePerm) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to create target directory: %v", err), http.StatusInternalServerError) + return + } + + // Open the zip file + zipReader, err := zip.NewReader(file, handler.Size) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to open zip file: %v", err), http.StatusInternalServerError) + return + } + + restoreDatabase := false + + // Extract each file from the zip archive + for _, zipFile := range zipReader.File { + // Open the file in the zip archive + rc, err := zipFile.Open() + if err != nil { + http.Error(w, fmt.Sprintf("Failed to open file in zip: %v", err), http.StatusInternalServerError) + return + } + defer rc.Close() + + // Create the corresponding file on disk + zipFile.Name = strings.ReplaceAll(zipFile.Name, "../", "") + fmt.Println("Restoring: " + strings.ReplaceAll(zipFile.Name, "\\", "/")) + if zipFile.Name == "sys.db" { + //Sysdb replacement. Close the database and restore + sysdb.Close() + restoreDatabase = true + } else if !strings.HasPrefix(strings.ReplaceAll(zipFile.Name, "\\", "/"), "conf/") { + //Malformed zip file. + http.Error(w, fmt.Sprintf("Invalid zip file structure or version too old"), http.StatusInternalServerError) + return + } + + //Check if parent dir exists + if !utils.FileExists(filepath.Dir(zipFile.Name)) { + os.MkdirAll(filepath.Dir(zipFile.Name), 0775) + } + + //Create the file + newFile, err := os.Create(zipFile.Name) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to create file: %v", err), http.StatusInternalServerError) + return + } + defer newFile.Close() + + // Copy the file contents from the zip to the new file + _, err = io.Copy(newFile, rc) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to extract file from zip: %v", err), http.StatusInternalServerError) + return + } + } + + // Send a success response + w.WriteHeader(http.StatusOK) + log.Println("Configuration restored") + fmt.Fprintln(w, "Configuration restored") + + if restoreDatabase { + go func() { + log.Println("Database altered. Restarting in 3 seconds...") + time.Sleep(3 * time.Second) + os.Exit(0) + }() + + } + +} diff --git a/src/main.go b/src/main.go index ede2958..05bb2ef 100644 --- a/src/main.go +++ b/src/main.go @@ -83,29 +83,34 @@ func SetupCloseHandler() { signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c - fmt.Println("- Shutting down " + name) - fmt.Println("- Closing GeoDB ") - geodbStore.Close() - fmt.Println("- Closing Netstats Listener") - netstatBuffers.Close() - fmt.Println("- Closing Statistic Collector") - statisticCollector.Close() - fmt.Println("- Stopping mDNS Discoverer") - //Stop the mdns service - mdnsTickerStop <- true - mdnsScanner.Close() - - //Remove the tmp folder - fmt.Println("- Cleaning up tmp files") - os.RemoveAll("./tmp") - - //Close database, final - fmt.Println("- Stopping system database") - sysdb.Close() + ShutdownSeq() os.Exit(0) }() } +func ShutdownSeq() { + fmt.Println("- Shutting down " + name) + fmt.Println("- Closing GeoDB ") + geodbStore.Close() + fmt.Println("- Closing Netstats Listener") + netstatBuffers.Close() + fmt.Println("- Closing Statistic Collector") + statisticCollector.Close() + fmt.Println("- Stopping mDNS Discoverer") + //Stop the mdns service + mdnsTickerStop <- true + mdnsScanner.Close() + fmt.Println("- Closing Certificates Auto Renewer") + acmeAutoRenewer.Close() + //Remove the tmp folder + fmt.Println("- Cleaning up tmp files") + os.RemoveAll("./tmp") + + //Close database, final + fmt.Println("- Stopping system database") + sysdb.Close() +} + func main() { //Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information handler = aroz.HandleFlagParse(aroz.ServiceInfo{ diff --git a/src/mod/acme/acme.go b/src/mod/acme/acme.go index ae7e138..ec8bc1c 100644 --- a/src/mod/acme/acme.go +++ b/src/mod/acme/acme.go @@ -134,12 +134,12 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email // Each certificate comes back with the cert bytes, the bytes of the client's // private key, and a certificate URL. - err = ioutil.WriteFile("./certs/"+certificateName+".crt", certificates.Certificate, 0777) + err = ioutil.WriteFile("./conf/certs/"+certificateName+".crt", certificates.Certificate, 0777) if err != nil { log.Println(err) return false, err } - err = ioutil.WriteFile("./certs/"+certificateName+".key", certificates.PrivateKey, 0777) + err = ioutil.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777) if err != nil { log.Println(err) return false, err @@ -154,7 +154,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email // it will said expired as well! func (a *ACMEHandler) CheckCertificate() []string { // read from dir - filenames, err := os.ReadDir("./certs/") + filenames, err := os.ReadDir("./conf/certs/") expiredCerts := []string{} @@ -164,7 +164,7 @@ func (a *ACMEHandler) CheckCertificate() []string { } for _, filename := range filenames { - certFilepath := filepath.Join("./certs/", filename.Name()) + certFilepath := filepath.Join("./conf/certs/", filename.Name()) certBytes, err := os.ReadFile(certFilepath) if err != nil { diff --git a/src/mod/acme/autorenew.go b/src/mod/acme/autorenew.go index 47123f9..211e168 100644 --- a/src/mod/acme/autorenew.go +++ b/src/mod/acme/autorenew.go @@ -341,6 +341,12 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) { return a.renewExpiredDomains(expiredCertList) } +func (a *AutoRenewer) Close() { + if a.TickerstopChan != nil { + a.TickerstopChan <- true + } +} + // Renew the certificate by filename extract all DNS name from the // certificate and renew them one by one by calling to the acmeHandler func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, error) { diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index b84a721..830d2e8 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -91,11 +91,12 @@ func NewDynamicProxyCore(target *url.URL, prepender string, ignoreTLSVerificatio //Hack the default transporter to handle more connections thisTransporter := http.DefaultTransport - thisTransporter.(*http.Transport).MaxIdleConns = 3000 - thisTransporter.(*http.Transport).MaxIdleConnsPerHost = 3000 - thisTransporter.(*http.Transport).IdleConnTimeout = 10 * time.Second - thisTransporter.(*http.Transport).MaxConnsPerHost = 0 - //thisTransporter.(*http.Transport).DisableCompression = true + optimalConcurrentConnection := 32 + thisTransporter.(*http.Transport).MaxIdleConns = optimalConcurrentConnection * 2 + thisTransporter.(*http.Transport).MaxIdleConnsPerHost = optimalConcurrentConnection + thisTransporter.(*http.Transport).IdleConnTimeout = 30 * time.Second + thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2 + thisTransporter.(*http.Transport).DisableCompression = true if ignoreTLSVerification { //Ignore TLS certificate validation error diff --git a/src/mod/dynamicproxy/dpcore/dpcore_test.go b/src/mod/dynamicproxy/dpcore/dpcore_test.go new file mode 100644 index 0000000..297be72 --- /dev/null +++ b/src/mod/dynamicproxy/dpcore/dpcore_test.go @@ -0,0 +1,49 @@ +package dpcore_test + +import ( + "testing" + + "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" +) + +func TestReplaceLocationHost(t *testing.T) { + urlString := "http://private.com/test/newtarget/" + rrr := &dpcore.ResponseRewriteRuleSet{ + OriginalHost: "test.example.com", + ProxyDomain: "private.com/test", + UseTLS: true, + } + useTLS := true + + expectedResult := "https://test.example.com/newtarget/" + + result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS) + if err != nil { + t.Errorf("Error occurred: %v", err) + } + + if result != expectedResult { + t.Errorf("Expected: %s, but got: %s", expectedResult, result) + } +} + +func TestReplaceLocationHostRelative(t *testing.T) { + urlString := "api/" + rrr := &dpcore.ResponseRewriteRuleSet{ + OriginalHost: "test.example.com", + ProxyDomain: "private.com/test", + UseTLS: true, + } + useTLS := true + + expectedResult := "https://test.example.com/api/" + + result, err := dpcore.ReplaceLocationHost(urlString, rrr, useTLS) + if err != nil { + t.Errorf("Error occurred: %v", err) + } + + if result != expectedResult { + t.Errorf("Expected: %s, but got: %s", expectedResult, result) + } +} diff --git a/src/mod/dynamicproxy/dpcore/utils.go b/src/mod/dynamicproxy/dpcore/utils.go index 6f1b823..12c7f78 100644 --- a/src/mod/dynamicproxy/dpcore/utils.go +++ b/src/mod/dynamicproxy/dpcore/utils.go @@ -44,3 +44,8 @@ func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS b return u.String(), nil } + +// Debug functions +func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) { + return replaceLocationHost(urlString, rrr, useTLS) +} diff --git a/src/mod/dynamicproxy/redirection/handler.go b/src/mod/dynamicproxy/redirection/handler.go index 0b15378..44f4d42 100644 --- a/src/mod/dynamicproxy/redirection/handler.go +++ b/src/mod/dynamicproxy/redirection/handler.go @@ -30,8 +30,13 @@ func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) int { redirectTarget := rr.TargetURL if rr.ForwardChildpath { - //Remove the first / in the path - redirectTarget += strings.TrimPrefix(r.URL.Path, "/") + //Remove the first / in the path if the redirect target already have tailing slash + if strings.HasSuffix(redirectTarget, "/") { + redirectTarget += strings.TrimPrefix(r.URL.Path, "/") + } else { + redirectTarget += r.URL.Path + } + if r.URL.RawQuery != "" { redirectTarget += "?" + r.URL.RawQuery } diff --git a/src/mod/ganserv/authkeyLinux.go b/src/mod/ganserv/authkeyLinux.go index abe5079..d5fe850 100644 --- a/src/mod/ganserv/authkeyLinux.go +++ b/src/mod/ganserv/authkeyLinux.go @@ -13,8 +13,8 @@ import ( ) func readAuthTokenAsAdmin() (string, error) { - if utils.FileExists("./authtoken.secret") { - authKey, err := os.ReadFile("./authtoken.secret") + if utils.FileExists("./conf/authtoken.secret") { + authKey, err := os.ReadFile("./conf/authtoken.secret") if err == nil { return strings.TrimSpace(string(authKey)), nil } diff --git a/src/mod/ganserv/authkeyWin.go b/src/mod/ganserv/authkeyWin.go index 40bbfc8..d1e7c1c 100644 --- a/src/mod/ganserv/authkeyWin.go +++ b/src/mod/ganserv/authkeyWin.go @@ -19,8 +19,8 @@ import ( // Use admin permission to read auth token on Windows func readAuthTokenAsAdmin() (string, error) { //Check if the previous startup already extracted the authkey - if utils.FileExists("./authtoken.secret") { - authKey, err := os.ReadFile("./authtoken.secret") + if utils.FileExists("./conf/authtoken.secret") { + authKey, err := os.ReadFile("./conf/authtoken.secret") if err == nil { return strings.TrimSpace(string(authKey)), nil } @@ -30,7 +30,7 @@ func readAuthTokenAsAdmin() (string, error) { exe := "cmd.exe" cwd, _ := os.Getwd() - output, _ := filepath.Abs(filepath.Join("./", "authtoken.secret")) + output, _ := filepath.Abs(filepath.Join("./conf/", "authtoken.secret")) os.WriteFile(output, []byte(""), 0775) args := fmt.Sprintf("/C type \"C:\\ProgramData\\ZeroTier\\One\\authtoken.secret\" > \"" + output + "\"") @@ -49,13 +49,13 @@ func readAuthTokenAsAdmin() (string, error) { log.Println("Please click agree to allow access to ZeroTier authtoken from ProgramData") retry := 0 time.Sleep(3 * time.Second) - for !utils.FileExists("./authtoken.secret") && retry < 10 { + for !utils.FileExists("./conf/authtoken.secret") && retry < 10 { time.Sleep(3 * time.Second) log.Println("Waiting for ZeroTier authtoken extraction...") retry++ } - authKey, err := os.ReadFile("./authtoken.secret") + authKey, err := os.ReadFile("./conf/authtoken.secret") if err != nil { return "", err } diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 754fd6e..a5f8159 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -73,7 +73,7 @@ func ReverseProxtInit() { dynamicProxyRouter = dprouter //Load all conf from files - confs, _ := filepath.Glob("./conf/*.config") + confs, _ := filepath.Glob("./conf/proxy/*.config") for _, conf := range confs { record, err := LoadReverseProxyConfig(conf) if err != nil { diff --git a/src/start.go b/src/start.go index 90dd8df..a57119e 100644 --- a/src/start.go +++ b/src/start.go @@ -49,8 +49,9 @@ func startupSequence() { //Create tables for the database sysdb.NewTable("settings") - //Create tmp folder + //Create tmp folder and conf folder os.MkdirAll("./tmp", 0775) + os.MkdirAll("./conf/proxy/", 0775) //Create an auth agent sessionKey, err := auth.GetSessionKey(sysdb) @@ -63,13 +64,13 @@ func startupSequence() { }) //Create a TLS certificate manager - tlsCertManager, err = tlscert.NewManager("./certs", development) + tlsCertManager, err = tlscert.NewManager("./conf/certs", development) if err != nil { panic(err) } //Create a redirection rule table - redirectTable, err = redirection.NewRuleTable("./rules/redirect") + redirectTable, err = redirection.NewRuleTable("./conf/redirect") if err != nil { panic(err) } @@ -104,7 +105,7 @@ func startupSequence() { pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{ Enabled: false, - ConfigFolder: "./rules/pathrules", + ConfigFolder: "./conf/rules/pathrules", }) /* @@ -197,7 +198,7 @@ func startupSequence() { Obtaining certificates from ACME Server */ acmeHandler = initACME() - acmeAutoRenewer, err = acme.NewAutoRenewer("./rules/acme_conf.json", "./certs/", int64(*acmeAutoRenewInterval), acmeHandler) + acmeAutoRenewer, err = acme.NewAutoRenewer("./conf/acme_conf.json", "./conf/certs/", int64(*acmeAutoRenewInterval), acmeHandler) if err != nil { log.Fatal(err) } diff --git a/src/web/components/subd.html b/src/web/components/subd.html index 2bd193e..232f639 100644 --- a/src/web/components/subd.html +++ b/src/web/components/subd.html @@ -43,10 +43,17 @@ tlsIcon = ``; } + let tlsVerificationField = ""; + if (subd.RequireTLS){ + tlsVerificationField = !subd.SkipCertValidations?``:`` + }else{ + tlsVerificationField = "N/A" + } + $("#subdList").append(` ${subd.RootOrMatchingDomain} ${subd.Domain} ${tlsIcon} - ${!subd.SkipCertValidations?``:``} + ${tlsVerificationField} ${subd.RequireBasicAuth?``:``} diff --git a/src/web/components/utils.html b/src/web/components/utils.html index b8056a7..0c463e5 100644 --- a/src/web/components/utils.html +++ b/src/web/components/utils.html @@ -116,7 +116,11 @@

Results:

N/A

- + +
+

System Backup & Restore

+

Options related to system backup, migrate and restore.

+
diff --git a/src/web/components/vdir.html b/src/web/components/vdir.html index edb77fe..a62e1f7 100644 --- a/src/web/components/vdir.html +++ b/src/web/components/vdir.html @@ -44,10 +44,18 @@ if (vdir.RequireTLS){ tlsIcon = ``; } + + let tlsVerificationField = ""; + if (vdir.RequireTLS){ + tlsVerificationField = !vdir.SkipCertValidations?``:`` + }else{ + tlsVerificationField = "N/A" + } + $("#vdirList").append(` ${vdir.RootOrMatchingDomain} ${vdir.Domain} ${tlsIcon} - ${!vdir.SkipCertValidations?``:``} + ${tlsVerificationField} ${vdir.RequireBasicAuth?``:``} diff --git a/src/web/snippet/configTools.html b/src/web/snippet/configTools.html new file mode 100644 index 0000000..02cd143 --- /dev/null +++ b/src/web/snippet/configTools.html @@ -0,0 +1,100 @@ + + + + + + + + + +
+
+
+
+ Config Export and Import Tool +
Painless migration with one click
+
+
+

Backup Current Configs

+

This will download all your configuration on zoraxy in a zip file. This includes all the proxy configs and certificates. Please keep it somewhere safe and after migration, delete this if possible.

+
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+
+ +
+ +

Restore from Config

+

You can restore your previous settings and database from a zip file config backup. +
RESTORE FULL SYSTEM SNAPSHOT WILL CAUSE THE SYSTEM TO SHUTDOWN AFTER COMPLETED. Make sure your Zoraxy is configured to work with systemd to automatic restart Zoraxy after system restore completed.
+ +

+
+ + +
+ The current config will be backup to ./conf_old{timestamp}. If you screw something up, you can always restore it by ssh to your host and restore the configs from the old folder. +

+ +
+ + + \ No newline at end of file