mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00

- Added user defined polling and propagation timeout option in ACME - Updated lego and added a few new DNS challenge providers - Updated code gen to support new parameters
320 lines
8.4 KiB
Go
320 lines
8.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
/*
|
|
Usage
|
|
|
|
git clone {repo_link_for_lego}
|
|
go run extract.go
|
|
//go run extract.go -- "win7"
|
|
|
|
*/
|
|
|
|
var legoProvidersSourceFolder string = "./lego/providers/dns/"
|
|
var outputDir string = "./acmedns"
|
|
var defTemplate string = `package acmedns
|
|
/*
|
|
THIS MODULE IS GENERATED AUTOMATICALLY
|
|
DO NOT EDIT THIS FILE
|
|
*/
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/go-acme/lego/v4/challenge"
|
|
{{imports}}
|
|
)
|
|
|
|
//name is the DNS provider name, e.g. cloudflare or gandi
|
|
//JSON (js) must be in key-value string that match ConfigableFields Title in providers.json, e.g. {"Username":"far","Password":"boo"}
|
|
func GetDNSProviderByJsonConfig(name string, js string, propagationTimeout int64, pollingInterval int64)(challenge.Provider, error){
|
|
pgDuration := time.Duration(propagationTimeout) * time.Second
|
|
plInterval := time.Duration(pollingInterval) * time.Second
|
|
switch name {
|
|
{{magic}}
|
|
default:
|
|
return nil, fmt.Errorf("unrecognized DNS provider: %s", name)
|
|
}
|
|
}
|
|
`
|
|
|
|
type Field struct {
|
|
Title string
|
|
Datatype string
|
|
}
|
|
type ProviderInfo struct {
|
|
Name string //Name of this provider
|
|
ConfigableFields []*Field //Field that shd be expose to user for filling in
|
|
HiddenFields []*Field //Fields that is usable but shd be hidden from user
|
|
}
|
|
|
|
func fileExists(filePath string) bool {
|
|
_, err := os.Stat(filePath)
|
|
if err == nil {
|
|
return true
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
// For other errors, you may handle them differently
|
|
return false
|
|
}
|
|
|
|
// This function define the DNS not supported by zoraxy
|
|
func getExcludedDNSProviders() []string {
|
|
return []string{
|
|
"edgedns", //Too complex data structure
|
|
"exec", //Not a DNS provider
|
|
"httpreq", //Not a DNS provider
|
|
"hurricane", //Multi-credentials arch
|
|
"oraclecloud", //Evil company
|
|
"acmedns", //Not a DNS provider
|
|
"selectelv2", //Not sure why not working with our code generator
|
|
"designate", //OpenStack, if you are using this you shd not be using zoraxy
|
|
"mythicbeasts", //Module require url.URL, which cannot be automatically parsed
|
|
|
|
//The following are incomaptible with Zoraxy due to dependencies issue,
|
|
//might be resolved in future
|
|
"corenetworks",
|
|
"timewebcloud",
|
|
"volcengine",
|
|
"exoscale",
|
|
}
|
|
}
|
|
|
|
// Exclude list for Windows build, due to limitations for lego versions
|
|
func getExcludedDNSProvidersNT61() []string {
|
|
fmt.Println("Windows 7 support is deprecated, please consider upgrading to a newer version of Windows.")
|
|
return append(getExcludedDNSProviders(), []string{"cpanel",
|
|
"mailinabox",
|
|
"shellrent",
|
|
}...)
|
|
}
|
|
|
|
func isInSlice(str string, slice []string) bool {
|
|
for _, s := range slice {
|
|
if s == str {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isExcludedDNSProvider(providerName string) bool {
|
|
return isInSlice(providerName, getExcludedDNSProviders())
|
|
}
|
|
|
|
func isExcludedDNSProviderNT61(providerName string) bool {
|
|
return isInSlice(providerName, getExcludedDNSProvidersNT61())
|
|
}
|
|
|
|
// extractConfigStruct extracts the name of the config struct and its content as plain text from a given source code.
|
|
func extractConfigStruct(sourceCode string) (string, string) {
|
|
// Regular expression to match the struct declaration.
|
|
structRegex := regexp.MustCompile(`type\s+([A-Za-z0-9_]+)\s+struct\s*{([^{}]*)}`)
|
|
|
|
// Find the first match of the struct declaration.
|
|
match := structRegex.FindStringSubmatch(sourceCode)
|
|
if len(match) < 3 {
|
|
return "", "" // No match found
|
|
}
|
|
|
|
// Extract the struct name and its content.
|
|
structName := match[1]
|
|
structContent := match[2]
|
|
if structName != "Config" {
|
|
allStructs := structRegex.FindAllStringSubmatch(sourceCode, 10)
|
|
for _, thisStruct := range allStructs {
|
|
//fmt.Println("Name => ", test[1])
|
|
//fmt.Println("Content => ", test[2])
|
|
|
|
if thisStruct[1] == "Config" {
|
|
structName = "Config"
|
|
structContent = thisStruct[2]
|
|
break
|
|
}
|
|
}
|
|
|
|
if structName != "Config" {
|
|
panic("Unable to find Config for this provider")
|
|
}
|
|
}
|
|
|
|
return structName, structContent
|
|
}
|
|
|
|
func main() {
|
|
// A map of provider name to information on what can be filled
|
|
extractedProviderList := map[string]*ProviderInfo{}
|
|
buildForWindowsSeven := (len(os.Args) > 2 && os.Args[2] == "win7")
|
|
|
|
//Search all providers
|
|
providers, err := filepath.Glob(filepath.Join(legoProvidersSourceFolder, "/*"))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
//Create output folder if not exists
|
|
err = os.MkdirAll(outputDir, 0775)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
generatedConvertcode := ""
|
|
importList := ""
|
|
for _, provider := range providers {
|
|
providerName := filepath.Base(provider)
|
|
|
|
if buildForWindowsSeven {
|
|
if isExcludedDNSProviderNT61(providerName) {
|
|
//Ignore this provider
|
|
continue
|
|
}
|
|
} else {
|
|
if isExcludedDNSProvider(providerName) {
|
|
//Ignore this provider
|
|
continue
|
|
}
|
|
}
|
|
|
|
//Check if {provider_name}.go exists
|
|
providerDef := filepath.Join(provider, providerName+".go")
|
|
if !fileExists(providerDef) {
|
|
continue
|
|
}
|
|
|
|
fmt.Println("Extracting config structure for: " + providerDef)
|
|
providerSrc, err := os.ReadFile(providerDef)
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
return
|
|
}
|
|
_, strctContent := extractConfigStruct(string(providerSrc))
|
|
|
|
//Filter and write the content to json
|
|
/*
|
|
Example of stctContent (Note the tab prefix)
|
|
|
|
Token string
|
|
PropagationTimeout time.Duration
|
|
PollingInterval time.Duration
|
|
SequenceInterval time.Duration
|
|
HTTPClient *http.Client
|
|
*/
|
|
strctContentLines := strings.Split(strctContent, "\n")
|
|
configKeys := []*Field{}
|
|
hiddenKeys := []*Field{}
|
|
for _, lineDef := range strctContentLines {
|
|
fields := strings.Fields(lineDef)
|
|
if len(fields) < 2 || strings.HasPrefix(fields[0], "//") {
|
|
//Ignore this line
|
|
continue
|
|
}
|
|
|
|
//Filter out the fields that is not user-filled
|
|
switch fields[1] {
|
|
case "*url.URL":
|
|
fallthrough
|
|
case "string":
|
|
//Add exception rule for gandi baseURL
|
|
if (providerName == "gandi" || providerName == "gandiv5") && fields[0] == "BaseURL" {
|
|
//Not useful stuff. Ignore this field
|
|
continue
|
|
}
|
|
configKeys = append(configKeys, &Field{
|
|
Title: fields[0],
|
|
Datatype: fields[1],
|
|
})
|
|
case "int":
|
|
if fields[0] != "TTL" {
|
|
configKeys = append(configKeys, &Field{
|
|
Title: fields[0],
|
|
Datatype: fields[1],
|
|
})
|
|
} else if fields[0] == "TTL" {
|
|
//haveTTLField = true
|
|
} else {
|
|
hiddenKeys = append(hiddenKeys, &Field{
|
|
Title: fields[0],
|
|
Datatype: fields[1],
|
|
})
|
|
}
|
|
case "bool":
|
|
if fields[0] == "InsecureSkipVerify" || fields[0] == "SSLVerify" || fields[0] == "Debug" {
|
|
configKeys = append(configKeys, &Field{
|
|
Title: fields[0],
|
|
Datatype: fields[1],
|
|
})
|
|
} else {
|
|
hiddenKeys = append(hiddenKeys, &Field{
|
|
Title: fields[0],
|
|
Datatype: fields[1],
|
|
})
|
|
}
|
|
case "time.Duration":
|
|
if fields[0] == "PropagationTimeout" || fields[0] == "PollingInterval" {
|
|
configKeys = append(configKeys, &Field{
|
|
Title: fields[0],
|
|
Datatype: fields[1],
|
|
})
|
|
}
|
|
|
|
default:
|
|
//Not used fields
|
|
hiddenKeys = append(hiddenKeys, &Field{
|
|
Title: fields[0],
|
|
Datatype: fields[1],
|
|
})
|
|
}
|
|
}
|
|
fmt.Println(strctContent)
|
|
|
|
extractedProviderList[providerName] = &ProviderInfo{
|
|
Name: providerName,
|
|
ConfigableFields: configKeys,
|
|
HiddenFields: hiddenKeys,
|
|
}
|
|
|
|
//Generate the code for converting incoming json into target config
|
|
codeSegment := `
|
|
case "` + providerName + `":
|
|
cfg := ` + providerName + `.NewDefaultConfig()
|
|
err := json.Unmarshal([]byte(js), &cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfg.PropagationTimeout = pgDuration
|
|
cfg.PollingInterval = plInterval
|
|
return ` + providerName + `.NewDNSProviderConfig(cfg)`
|
|
generatedConvertcode += codeSegment
|
|
importList += ` "github.com/go-acme/lego/v4/providers/dns/` + providerName + "\"\n"
|
|
}
|
|
|
|
js, err := json.MarshalIndent(extractedProviderList, "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fullCodeSnippet := strings.ReplaceAll(defTemplate, "{{magic}}", generatedConvertcode)
|
|
fullCodeSnippet = strings.ReplaceAll(fullCodeSnippet, "{{imports}}", importList)
|
|
|
|
outJsonFilename := "providers.json"
|
|
outGoFilename := "acmedns.go"
|
|
if buildForWindowsSeven {
|
|
outJsonFilename = "providers_nt61.json"
|
|
outGoFilename = "acmedns_nt61.go"
|
|
}
|
|
os.WriteFile(filepath.Join(outputDir, outJsonFilename), js, 0775)
|
|
os.WriteFile(filepath.Join(outputDir, outGoFilename), []byte(fullCodeSnippet), 0775)
|
|
fmt.Println("Output written to file")
|
|
}
|