//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver package main import ( "flag" "fmt" "log" "net/http" "os" "strings" "git.rootprojects.org/root/telebit/mgmt/authstore" "github.com/go-acme/lego/v3/challenge" "github.com/go-acme/lego/v3/providers/dns/duckdns" "github.com/go-acme/lego/v3/providers/dns/godaddy" "github.com/go-acme/lego/v3/providers/dns/namedotcom" _ "github.com/joho/godotenv/autoload" ) var ( // GitRev refers to the abbreviated commit hash GitRev = "0000000" // GitVersion refers to the most recent tag, plus any commits made since then GitVersion = "v0.0.0-pre0+0000000" // GitTimestamp refers to the timestamp of the most recent commit GitTimestamp = "0000-00-00T00:00:00+0000" ) // MWKey is a type guard type MWKey string var store authstore.Store var provider challenge.Provider = nil // TODO is this concurrency-safe? var secret string var primaryDomain string var relayDomain string func help() { fmt.Fprintf(os.Stderr, "Usage: mgmt --domain --secret <128-bit secret>\n") } func main() { var err error var port string var lnAddr string var dbURL string var challengesPort string flag.StringVar(&port, "port", "", "port to listen to (default localhost 3000)") flag.StringVar(&lnAddr, "listen", "", "IPv4 or IPv6 bind address + port (instead of --port)") flag.StringVar(&challengesPort, "challenges-port", "80", "port to use to respond to .well-known/acme-challenge tokens") flag.StringVar(&dbURL, "db-url", "postgres://postgres:postgres@localhost:5432/postgres", "database (postgres) connection url") flag.StringVar(&secret, "secret", "", "a >= 16-character random string for JWT key signing") flag.StringVar(&primaryDomain, "domain", "", "the base domain to use for all clients") flag.StringVar(&relayDomain, "tunnel-domain", "", "the domain name of the tunnel relay service, if different from base domain") flag.Parse() if 0 == len(primaryDomain) { primaryDomain = os.Getenv("DOMAIN") } if 0 == len(relayDomain) { relayDomain = os.Getenv("TUNNEL_DOMAIN") } if 0 == len(relayDomain) { relayDomain = primaryDomain } if 0 == len(dbURL) { dbURL = os.Getenv("DB_URL") } if 0 == len(secret) { secret = os.Getenv("SECRET") } // prefer --listen (with address) over --port (localhost only) if 0 == len(lnAddr) { lnAddr = os.Getenv("LISTEN") } if 0 == len(lnAddr) { if 0 == len(port) { port = os.Getenv("PORT") } if 0 == len(port) { port = "3000" } lnAddr = "localhost:" + port } if len(os.Getenv("GODADDY_API_KEY")) > 0 { id := os.Getenv("GODADDY_API_KEY") apiSecret := os.Getenv("GODADDY_API_SECRET") if provider, err = newGoDaddyDNSProvider(id, apiSecret); nil != err { panic(err) } } else if len(os.Getenv("DUCKDNS_TOKEN")) > 0 { if provider, err = newDuckDNSProvider(os.Getenv("DUCKDNS_TOKEN")); nil != err { panic(err) } } else if len(os.Getenv("NAMECOM_API_TOKEN")) > 0 { if provider, err = newNameDotComDNSProvider( os.Getenv("NAMECOM_USERNAME"), os.Getenv("NAMECOM_API_TOKEN"), ); nil != err { panic(err) } } else { fmt.Println("DNS-01 relay disabled") } if 0 == len(primaryDomain) || 0 == len(secret) || 0 == len(dbURL) { help() os.Exit(1) return } connStr := dbURL // TODO url.Parse if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") { connStr += "?sslmode=disable" } else { connStr += "?sslmode=required" } store, err = authstore.NewStore(connStr, initSQL) if nil != err { log.Fatal("connection error", err) return } _ = store.SetMaster(secret) defer store.Close() go func() { fmt.Println("Listening for ACME challenges on :" + challengesPort) if err := http.ListenAndServe(":"+challengesPort, routeStatic()); nil != err { log.Fatal(err) os.Exit(1) } }() fmt.Println("Listening on", lnAddr) fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(lnAddr, routeAll())) } // newNameDotComDNSProvider is for the sake of demoing the tunnel func newNameDotComDNSProvider(username, apitoken string) (*namedotcom.DNSProvider, error) { config := namedotcom.NewDefaultConfig() config.Username = username config.APIToken = apitoken return namedotcom.NewDNSProviderConfig(config) } // newDuckDNSProvider is for the sake of demoing the tunnel func newDuckDNSProvider(token string) (*duckdns.DNSProvider, error) { config := duckdns.NewDefaultConfig() config.Token = token return duckdns.NewDNSProviderConfig(config) } // newGoDaddyDNSProvider is for the sake of demoing the tunnel func newGoDaddyDNSProvider(id, secret string) (*godaddy.DNSProvider, error) { config := godaddy.NewDefaultConfig() config.APIKey = id config.APISecret = secret return godaddy.NewDNSProviderConfig(config) }