Browse Source

Merge branch 'develop' into feature_v0.15.1_apidocs

cameronts 2 years ago
parent
commit
9998193071
47 changed files with 532 additions and 431 deletions
  1. 3 3
      README.md
  2. 7 1
      controllers/ext_client.go
  3. 11 16
      controllers/node.go
  4. 42 41
      controllers/security.go
  5. 8 6
      go.mod
  6. 25 88
      go.sum
  7. BIN
      img/graph-readme.gif
  8. BIN
      img/mesh-diagram.png
  9. BIN
      img/netmaker-teal.png
  10. BIN
      img/netmaker.png
  11. BIN
      img/readme.gif
  12. BIN
      img/visit-website.gif
  13. BIN
      img/y-combinator.png
  14. 1 0
      logic/nodes.go
  15. 2 2
      logic/peers.go
  16. 1 1
      logic/server.go
  17. 2 1
      logic/wireguard.go
  18. 3 4
      main.go
  19. 5 0
      models/error.go
  20. 17 2
      models/node.go
  21. 5 0
      models/structs.go
  22. 19 23
      mq/mq.go
  23. 5 0
      mq/publishers.go
  24. 5 4
      mq/util.go
  25. 26 0
      netclient/cli_options/cmds.go
  26. 26 1
      netclient/command/commands.go
  27. 1 0
      netclient/daemon/freebsd.go
  28. 3 0
      netclient/daemon/systemd.go
  29. 3 0
      netclient/functions/clientconfig.go
  30. 111 114
      netclient/functions/common.go
  31. 54 0
      netclient/functions/connection.go
  32. 20 20
      netclient/functions/daemon.go
  33. 1 6
      netclient/functions/join.go
  34. 1 1
      netclient/functions/localport.go
  35. 34 33
      netclient/functions/mqhandlers.go
  36. 11 8
      netclient/functions/mqpublish.go
  37. 1 0
      netclient/functions/upgrades/upgrades.go
  38. 22 0
      netclient/functions/upgrades/v0-14-8.go
  39. 3 0
      netclient/local/routes.go
  40. 3 0
      netclient/ncutils/iface.go
  41. 9 13
      netclient/wireguard/common.go
  42. 15 3
      netclient/wireguard/noquick.go
  43. 9 35
      netclient/wireguard/unix.go
  44. 4 1
      netclient/wireguard/windows.go
  45. 1 1
      scripts/nm-quick-interactive.sh
  46. 1 1
      scripts/nm-quick.sh
  47. 12 2
      serverctl/serverctl.go

+ 3 - 3
README.md

@@ -1,7 +1,7 @@
 
 
 <p align="center">
 <p align="center">
   <a href="https://netmaker.io">
   <a href="https://netmaker.io">
-  <img src="./img/netmaker-teal.png" width="50%"><break/>
+  <img src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/netmaker-teal.png" width="50%"><break/>
   </a>
   </a>
 </p>
 </p>
 
 
@@ -10,7 +10,7 @@
     <img src="https://runacap.com/wp-content/uploads/2022/06/ROSS_badge_white_Q1_2022.svg" alt="ROSS Index - Fastest Growing Open-Source Startups in Q1 2022 | Runa Capital"  width="15%"/>
     <img src="https://runacap.com/wp-content/uploads/2022/06/ROSS_badge_white_Q1_2022.svg" alt="ROSS Index - Fastest Growing Open-Source Startups in Q1 2022 | Runa Capital"  width="15%"/>
 </a>  
 </a>  
 <a href="https://www.ycombinator.com/companies/netmaker/" target="_blank" rel="noopener">
 <a href="https://www.ycombinator.com/companies/netmaker/" target="_blank" rel="noopener">
-    <img src="./img/y-combinator.png" alt="Y-Combinator" width="16%" />
+    <img src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/y-combinator.png" alt="Y-Combinator" width="16%" />
 </a>  
 </a>  
 
 
 </p>
 </p>
@@ -58,7 +58,7 @@
 3.a. (with custom domain + email): `wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh | sudo bash -s -- -d mynetmaker.domain.com -e [email protected]`    
 3.a. (with custom domain + email): `wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh | sudo bash -s -- -d mynetmaker.domain.com -e [email protected]`    
 
 
 <p float="left" align="middle">
 <p float="left" align="middle">
-<img src="./img/readme.gif" />
+<img src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/readme.gif" />
 </p>
 </p>
 
 
 After installing Netmaker, check out the [Walkthrough](https://itnext.io/getting-started-with-netmaker-a-wireguard-virtual-networking-platform-3d563fbd87f0) and [Getting Started](https://netmaker.readthedocs.io/en/master/getting-started.html) guides to learn more about configuring networks. Or, check out some of our other [Tutorials](https://www.netmaker.io/blog) for different use cases, including Kubernetes.
 After installing Netmaker, check out the [Walkthrough](https://itnext.io/getting-started-with-netmaker-a-wireguard-virtual-networking-platform-3d563fbd87f0) and [Getting Started](https://netmaker.readthedocs.io/en/master/getting-started.html) guides to learn more about configuring networks. Or, check out some of our other [Tutorials](https://www.netmaker.io/blog) for different use cases, including Kubernetes.

+ 7 - 1
controllers/ext_client.go

@@ -287,9 +287,9 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 
 
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
-
 	networkName := params["network"]
 	networkName := params["network"]
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
+	
 	ingressExists := checkIngressExists(nodeid)
 	ingressExists := checkIngressExists(nodeid)
 	if !ingressExists {
 	if !ingressExists {
 		err := errors.New("ingress does not exist")
 		err := errors.New("ingress does not exist")
@@ -300,6 +300,12 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 
 
 	var extclient models.ExtClient
 	var extclient models.ExtClient
+	var CustomExtClient models.CustomExtClient
+	
+	err := json.NewDecoder(r.Body).Decode(&CustomExtClient);
+	
+	if err == nil { extclient.ClientID = CustomExtClient.ClientID }
+	
 	extclient.Network = networkName
 	extclient.Network = networkName
 	extclient.IngressGatewayID = nodeid
 	extclient.IngressGatewayID = nodeid
 	node, err := logic.GetNodeByID(nodeid)
 	node, err := logic.GetNodeByID(nodeid)

+ 11 - 16
controllers/node.go

@@ -189,7 +189,7 @@ func nodeauth(next http.Handler) http.HandlerFunc {
 func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc {
 func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+			Code: http.StatusUnauthorized, Message: unauthorized_msg,
 		}
 		}
 
 
 		var params = mux.Vars(r)
 		var params = mux.Vars(r)
@@ -198,9 +198,6 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha
 		//check that the request is for a valid network
 		//check that the request is for a valid network
 		//if (networkCheck && !networkexists) || err != nil {
 		//if (networkCheck && !networkexists) || err != nil {
 		if networkCheck && !networkexists {
 		if networkCheck && !networkexists {
-			errorResponse = models.ErrorResponse{
-				Code: http.StatusNotFound, Message: "W1R3: This network does not exist. ",
-			}
 			returnErrorResponse(w, r, errorResponse)
 			returnErrorResponse(w, r, errorResponse)
 			return
 			return
 		} else {
 		} else {
@@ -218,9 +215,6 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha
 			if len(tokenSplit) > 1 {
 			if len(tokenSplit) > 1 {
 				authToken = tokenSplit[1]
 				authToken = tokenSplit[1]
 			} else {
 			} else {
-				errorResponse = models.ErrorResponse{
-					Code: http.StatusUnauthorized, Message: "W1R3: Missing Auth Token.",
-				}
 				returnErrorResponse(w, r, errorResponse)
 				returnErrorResponse(w, r, errorResponse)
 				return
 				return
 			}
 			}
@@ -237,9 +231,6 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha
 			var nodeID = ""
 			var nodeID = ""
 			username, networks, isadmin, errN := logic.VerifyUserToken(authToken)
 			username, networks, isadmin, errN := logic.VerifyUserToken(authToken)
 			if errN != nil {
 			if errN != nil {
-				errorResponse = models.ErrorResponse{
-					Code: http.StatusUnauthorized, Message: "W1R3: Unauthorized, Invalid Token Processed.",
-				}
 				returnErrorResponse(w, r, errorResponse)
 				returnErrorResponse(w, r, errorResponse)
 				return
 				return
 			}
 			}
@@ -272,9 +263,6 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha
 					} else {
 					} else {
 						node, err := logic.GetNodeByID(nodeID)
 						node, err := logic.GetNodeByID(nodeID)
 						if err != nil {
 						if err != nil {
-							errorResponse = models.ErrorResponse{
-								Code: http.StatusUnauthorized, Message: "W1R3: Missing Auth Token.",
-							}
 							returnErrorResponse(w, r, errorResponse)
 							returnErrorResponse(w, r, errorResponse)
 							return
 							return
 						}
 						}
@@ -293,9 +281,6 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha
 				}
 				}
 			}
 			}
 			if !isAuthorized {
 			if !isAuthorized {
-				errorResponse = models.ErrorResponse{
-					Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.",
-				}
 				returnErrorResponse(w, r, errorResponse)
 				returnErrorResponse(w, r, errorResponse)
 				return
 				return
 			} else {
 			} else {
@@ -334,6 +319,12 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
+	for _, node := range nodes {
+		if len(node.NetworkSettings.AccessKeys) > 0 {
+			node.NetworkSettings.AccessKeys = []models.AccessKey{} // not to be sent back to client; client already knows how to join the network
+		}
+	}
+
 	//Returns all the nodes in JSON format
 	//Returns all the nodes in JSON format
 	logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName)
 	logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
@@ -424,6 +415,10 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
+	if len(node.NetworkSettings.AccessKeys) > 0 {
+		node.NetworkSettings.AccessKeys = []models.AccessKey{} // not to be sent back to client; client already knows how to join the network
+	}
+
 	response := models.NodeGet{
 	response := models.NodeGet{
 		Node:         node,
 		Node:         node,
 		Peers:        peerUpdate.Peers,
 		Peers:        peerUpdate.Peers,

+ 42 - 41
controllers/security.go

@@ -2,7 +2,6 @@ package controller
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-	"errors"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
 
 
@@ -14,14 +13,23 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
+const (
+	master_uname     = "masteradministrator"
+	unauthorized_msg = "unauthorized"
+	unauthorized_err = models.Error(unauthorized_msg)
+)
+
 func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
 func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
+
 	return func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusUnauthorized, Message: "W1R3: It's not you it's me.",
+			Code: http.StatusUnauthorized, Message: unauthorized_msg,
 		}
 		}
 
 
 		var params = mux.Vars(r)
 		var params = mux.Vars(r)
 		bearerToken := r.Header.Get("Authorization")
 		bearerToken := r.Header.Get("Authorization")
+		// to have a custom DNS service adding entries
+		// we should refactor this, but is for the special case of an external service to query the DNS api
 		if strings.Contains(r.RequestURI, "/dns") && strings.ToUpper(r.Method) == "GET" && authenticateDNSToken(bearerToken) {
 		if strings.Contains(r.RequestURI, "/dns") && strings.ToUpper(r.Method) == "GET" && authenticateDNSToken(bearerToken) {
 			// do dns stuff
 			// do dns stuff
 			r.Header.Set("user", "nameserver")
 			r.Header.Set("user", "nameserver")
@@ -30,19 +38,17 @@ func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
 			next.ServeHTTP(w, r)
 			next.ServeHTTP(w, r)
 			return
 			return
 		}
 		}
-
-		networks, username, err := SecurityCheck(reqAdmin, params["networkname"], bearerToken)
+		var networkName = params["networkname"]
+		if len(networkName) == 0 {
+			networkName = params["network"]
+		}
+		networks, username, err := SecurityCheck(reqAdmin, networkName, bearerToken)
 		if err != nil {
 		if err != nil {
-			if strings.Contains(err.Error(), "does not exist") {
-				errorResponse.Code = http.StatusNotFound
-			}
-			errorResponse.Message = err.Error()
 			returnErrorResponse(w, r, errorResponse)
 			returnErrorResponse(w, r, errorResponse)
 			return
 			return
 		}
 		}
 		networksJson, err := json.Marshal(&networks)
 		networksJson, err := json.Marshal(&networks)
 		if err != nil {
 		if err != nil {
-			errorResponse.Message = err.Error()
 			returnErrorResponse(w, r, errorResponse)
 			returnErrorResponse(w, r, errorResponse)
 			return
 			return
 		}
 		}
@@ -54,46 +60,33 @@ func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
 
 
 // SecurityCheck - checks token stuff
 // SecurityCheck - checks token stuff
 func SecurityCheck(reqAdmin bool, netname string, token string) ([]string, string, error) {
 func SecurityCheck(reqAdmin bool, netname string, token string) ([]string, string, error) {
-
-	var hasBearer = true
 	var tokenSplit = strings.Split(token, " ")
 	var tokenSplit = strings.Split(token, " ")
 	var authToken = ""
 	var authToken = ""
+	userNetworks := []string{}
 
 
 	if len(tokenSplit) < 2 {
 	if len(tokenSplit) < 2 {
-		hasBearer = false
+		return userNetworks, "", unauthorized_err
 	} else {
 	} else {
 		authToken = tokenSplit[1]
 		authToken = tokenSplit[1]
 	}
 	}
-	userNetworks := []string{}
 	//all endpoints here require master so not as complicated
 	//all endpoints here require master so not as complicated
-	isMasterAuthenticated := authenticateMaster(authToken)
-	username := ""
-	if !hasBearer || !isMasterAuthenticated {
-		userName, networks, isadmin, err := logic.VerifyUserToken(authToken)
-		username = userName
-		if err != nil {
-			return nil, username, errors.New("error verifying user token")
-		}
-		if !isadmin && reqAdmin {
-			return nil, username, errors.New("you are unauthorized to access this endpoint")
-		}
-		userNetworks = networks
-		if isadmin {
-			userNetworks = []string{ALL_NETWORK_ACCESS}
-		} else {
-			networkexists, err := functions.NetworkExists(netname)
-			if err != nil && !database.IsEmptyRecord(err) {
-				return nil, "", err
-			}
-			if netname != "" && !networkexists {
-				return nil, "", errors.New("this network does not exist")
-			}
-		}
-	} else if isMasterAuthenticated {
-		userNetworks = []string{ALL_NETWORK_ACCESS}
+	if authenticateMaster(authToken) {
+		return []string{ALL_NETWORK_ACCESS}, master_uname, nil
+	}
+	username, networks, isadmin, err := logic.VerifyUserToken(authToken)
+	if err != nil {
+		return nil, username, unauthorized_err
+	}
+	if !isadmin && reqAdmin {
+		return nil, username, unauthorized_err
+	}
+	userNetworks = networks
+	if isadmin {
+		return []string{ALL_NETWORK_ACCESS}, username, nil
 	}
 	}
-	if len(userNetworks) == 0 {
-		userNetworks = append(userNetworks, NO_NETWORKS_PRESENT)
+	// check network admin access
+	if len(netname) > 0 && (!authenticateNetworkUser(netname, userNetworks) || len(userNetworks) == 0) {
+		return nil, username, unauthorized_err
 	}
 	}
 	return userNetworks, username, nil
 	return userNetworks, username, nil
 }
 }
@@ -103,6 +96,14 @@ func authenticateMaster(tokenString string) bool {
 	return tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != ""
 	return tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != ""
 }
 }
 
 
+func authenticateNetworkUser(network string, userNetworks []string) bool {
+	networkexists, err := functions.NetworkExists(network)
+	if (err != nil && !database.IsEmptyRecord(err)) || !networkexists {
+		return false
+	}
+	return logic.StringSliceContains(userNetworks, network)
+}
+
 //Consider a more secure way of setting master key
 //Consider a more secure way of setting master key
 func authenticateDNSToken(tokenString string) bool {
 func authenticateDNSToken(tokenString string) bool {
 	tokens := strings.Split(tokenString, " ")
 	tokens := strings.Split(tokenString, " ")
@@ -115,7 +116,7 @@ func authenticateDNSToken(tokenString string) bool {
 func continueIfUserMatch(next http.Handler) http.HandlerFunc {
 func continueIfUserMatch(next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusUnauthorized, Message: "W1R3: This doesn't look like you.",
+			Code: http.StatusUnauthorized, Message: unauthorized_msg,
 		}
 		}
 		var params = mux.Vars(r)
 		var params = mux.Vars(r)
 		var requestedUser = params["username"]
 		var requestedUser = params["username"]

+ 8 - 6
go.mod

@@ -10,7 +10,7 @@ require (
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/mux v1.8.0
 	github.com/lib/pq v1.10.6
 	github.com/lib/pq v1.10.6
-	github.com/mattn/go-sqlite3 v1.14.10
+	github.com/mattn/go-sqlite3 v1.14.15
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.0
 	github.com/stretchr/testify v1.8.0
@@ -30,7 +30,7 @@ require (
 	fyne.io/fyne/v2 v2.2.3
 	fyne.io/fyne/v2 v2.2.3
 	github.com/c-robinson/iplib v1.0.3
 	github.com/c-robinson/iplib v1.0.3
 	github.com/cloverstd/tcping v0.1.1
 	github.com/cloverstd/tcping v0.1.1
-	github.com/guumaster/hostctl v1.1.2
+	github.com/guumaster/hostctl v1.1.3
 	github.com/kr/pretty v0.3.0
 	github.com/kr/pretty v0.3.0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 )
 )
@@ -43,11 +43,12 @@ require (
 require (
 require (
 	cloud.google.com/go/compute v1.7.0 // indirect
 	cloud.google.com/go/compute v1.7.0 // indirect
 	fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 // indirect
 	fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 // indirect
-	github.com/Microsoft/go-winio v0.4.14 // indirect
+	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
+	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/docker/distribution v2.7.1+incompatible // indirect
-	github.com/docker/docker v17.12.1-ce+incompatible // indirect
+	github.com/docker/distribution v2.8.1+incompatible // indirect
+	github.com/docker/docker v20.10.17+incompatible // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
@@ -75,11 +76,12 @@ require (
 	github.com/mdlayher/netlink v1.6.0 // indirect
 	github.com/mdlayher/netlink v1.6.0 // indirect
 	github.com/mdlayher/socket v0.1.1 // indirect
 	github.com/mdlayher/socket v0.1.1 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
-	github.com/opencontainers/image-spec v1.0.1 // indirect
+	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.9.0 // indirect
 	github.com/rogpeppe/go-internal v1.9.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/spf13/afero v1.9.2 // indirect
 	github.com/spf13/afero v1.9.2 // indirect
 	github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
 	github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
 	github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
 	github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect

+ 25 - 88
go.sum

@@ -65,22 +65,19 @@ fyne.io/fyne/v2 v2.2.3 h1:Umi3vVVW8XnWWPJmMkhIWQOMU/jxB1OqpWVUmjhODD0=
 fyne.io/fyne/v2 v2.2.3/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA=
 fyne.io/fyne/v2 v2.2.3/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA=
 fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4=
 fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4=
 fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
 fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
-github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
 github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
 github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
@@ -110,9 +107,7 @@ github.com/coreos/go-oidc/v3 v3.2.0 h1:2eR2MGR7thBXSQ2YbODlF0fcmgtliLCfr9iX6RW11
 github.com/coreos/go-oidc/v3 v3.2.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
 github.com/coreos/go-oidc/v3 v3.2.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -123,12 +118,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
-github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v17.12.1-ce+incompatible h1:JF3ixBk1BbHBmKGimGdei9/2mFcc2rKOReZ+nketjOI=
-github.com/docker/docker v17.12.1-ce+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
+github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE=
+github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
@@ -169,9 +162,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@@ -181,13 +171,9 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
 github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
 github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
 github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
 github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
 github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
 github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
 github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
 github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
@@ -195,7 +181,6 @@ github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBs
 github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
 github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
 github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -286,21 +271,14 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
 github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
 github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
 github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/guumaster/cligger v0.1.1/go.mod h1:7d2cVJR7sExHITuqWUU7S9inAs+Hx1QbMDZwO+21a64=
-github.com/guumaster/hostctl v1.1.2 h1:M+DrRaLLeBt7JTh3YbE607gCgRzyxOKuqyrpBC153LQ=
-github.com/guumaster/hostctl v1.1.2/go.mod h1:n5R/s1/tUbYNN1t3J/F/70ZGUWzExgJnEAS0YZ8VWg8=
-github.com/guumaster/logsymbols v0.3.1/go.mod h1:1M5/1js2Z7Yo8DRB3QrPURwqsXeOfgsJv1Utjookknw=
-github.com/guumaster/tablewriter v0.0.9 h1:qyswXhSCI1SWYH78MLApi8AfL8JsWZWAUkZLONNMiYI=
-github.com/guumaster/tablewriter v0.0.9/go.mod h1:9B1xy1BLPtcVAeYjC1EXPxcklqnzk7dU2c3ywGbUnKY=
+github.com/guumaster/hostctl v1.1.3 h1:b/yR3svkYsbr5VBdvfdyLXUl2xaKopSzgE/Xi7+1WRo=
+github.com/guumaster/hostctl v1.1.3/go.mod h1:h5rDx5Z8Hj2bYZfDt/eX4BNS2RSq7iRcGVQqfROJyH8=
+github.com/guumaster/tablewriter v0.0.10 h1:A0HD94yMdt4usgxBjoEceNeE0XMJ027euoHAzsPqBQs=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -325,7 +303,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
 github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
 github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -335,14 +312,9 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
 github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
 github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
 github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
 github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@@ -360,13 +332,9 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
-github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
+github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
 github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
 github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
 github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
 github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
 github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
@@ -386,24 +354,23 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
 github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
 github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
-github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
+github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -414,17 +381,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@@ -443,15 +401,14 @@ github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxr
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
 github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
@@ -460,21 +417,18 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -489,16 +443,13 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
 github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
 github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
 github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/txn2/txeh v1.3.0 h1:vnbv63htVMZCaQgLqVBxKvj2+HHHFUzNW7I183zjg3E=
 github.com/txn2/txeh v1.3.0 h1:vnbv63htVMZCaQgLqVBxKvj2+HHHFUzNW7I183zjg3E=
 github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw8=
 github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw8=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
 github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
 github.com/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA=
 github.com/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA=
 github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
 github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
@@ -526,11 +477,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -591,16 +539,13 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -682,10 +627,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+v
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -702,7 +644,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -714,7 +655,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -722,6 +662,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -773,10 +714,9 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -977,7 +917,6 @@ google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljW
 google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -1031,7 +970,6 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -1044,14 +982,13 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
 honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
 honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
 honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
 honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

BIN
img/graph-readme.gif


BIN
img/mesh-diagram.png


BIN
img/netmaker-teal.png


BIN
img/netmaker.png


BIN
img/readme.gif


BIN
img/visit-website.gif


BIN
img/y-combinator.png


+ 1 - 0
logic/nodes.go

@@ -433,6 +433,7 @@ func SetNodeDefaults(node *models.Node) {
 	node.SetDefaultIsDocker()
 	node.SetDefaultIsDocker()
 	node.SetDefaultIsK8S()
 	node.SetDefaultIsK8S()
 	node.SetDefaultIsHub()
 	node.SetDefaultIsHub()
+	node.SetDefaultConnected()
 }
 }
 
 
 // GetRecordKey - get record key
 // GetRecordKey - get record key

+ 2 - 2
logic/peers.go

@@ -272,7 +272,7 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet {
 		// remove internet gateway if server
 		// remove internet gateway if server
 		if node.IsServer == "yes" {
 		if node.IsServer == "yes" {
 			for i := len(egressIPs) - 1; i >= 0; i-- {
 			for i := len(egressIPs) - 1; i >= 0; i-- {
-				if egressIPs[i].IP.String() == "0.0.0.0/0" || egressIPs[i].IP.String() == "::/0" {
+				if egressIPs[i].String() == "0.0.0.0/0" || egressIPs[i].String() == "::/0" {
 					egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
 					egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
 				}
 				}
 			}
 			}
@@ -505,7 +505,7 @@ func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string)
 func getEgressIPs(node, peer *models.Node) []net.IPNet {
 func getEgressIPs(node, peer *models.Node) []net.IPNet {
 	//check for internet gateway
 	//check for internet gateway
 	internetGateway := false
 	internetGateway := false
-	if slices.Contains(peer.EgressGatewayRanges, "0.0.0.0/0") || slices.Contains(peer.EgressGatewayRanges, "::0") {
+	if slices.Contains(peer.EgressGatewayRanges, "0.0.0.0/0") || slices.Contains(peer.EgressGatewayRanges, "::/0") {
 		internetGateway = true
 		internetGateway = true
 	}
 	}
 	allowedips := []net.IPNet{}
 	allowedips := []net.IPNet{}

+ 1 - 1
logic/server.go

@@ -154,7 +154,7 @@ func ServerJoin(networkSettings *models.Network) (models.Node, error) {
 		return returnNode, err
 		return returnNode, err
 	}
 	}
 
 
-	err = wireguard.InitWireguard(node, privateKey, peers.Peers, false)
+	err = wireguard.InitWireguard(node, privateKey, peers.Peers)
 	if err != nil {
 	if err != nil {
 		return returnNode, err
 		return returnNode, err
 	}
 	}

+ 2 - 1
logic/wireguard.go

@@ -58,6 +58,7 @@ func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 		newNode.MTU != currentNode.MTU ||
 		newNode.MTU != currentNode.MTU ||
 		newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
 		newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
 		newNode.DNSOn != currentNode.DNSOn ||
 		newNode.DNSOn != currentNode.DNSOn ||
+		newNode.Connected != currentNode.Connected ||
 		len(newNode.AllowedIPs) != len(currentNode.AllowedIPs) {
 		len(newNode.AllowedIPs) != len(currentNode.AllowedIPs) {
 		return true
 		return true
 	}
 	}
@@ -139,7 +140,7 @@ func setWGConfig(node *models.Node, peerupdate bool) error {
 		}
 		}
 		logger.Log(2, "updated peers on server", node.Name)
 		logger.Log(2, "updated peers on server", node.Name)
 	} else {
 	} else {
-		err = wireguard.InitWireguard(node, privkey, peers.Peers, false)
+		err = wireguard.InitWireguard(node, privkey, peers.Peers)
 		logger.Log(3, "finished setting wg config on server", node.Name)
 		logger.Log(3, "finished setting wg config on server", node.Name)
 	}
 	}
 	return err
 	return err

+ 3 - 4
main.go

@@ -86,9 +86,9 @@ func initialize() { // Client Mode Prereq Check
 		logger.Log(0, "no OAuth provider found or not configured, continuing without OAuth")
 		logger.Log(0, "no OAuth provider found or not configured, continuing without OAuth")
 	}
 	}
 
 
-	err = serverctl.SetDefaultACLS()
+	err = serverctl.SetDefaults()
 	if err != nil {
 	if err != nil {
-		logger.FatalLog("error setting default acls: ", err.Error())
+		logger.FatalLog("error setting defaults: ", err.Error())
 	}
 	}
 
 
 	if servercfg.IsClientMode() != "off" {
 	if servercfg.IsClientMode() != "off" {
@@ -171,7 +171,7 @@ func runMessageQueue(wg *sync.WaitGroup) {
 	defer wg.Done()
 	defer wg.Done()
 	brokerHost, secure := servercfg.GetMessageQueueEndpoint()
 	brokerHost, secure := servercfg.GetMessageQueueEndpoint()
 	logger.Log(0, "connecting to mq broker at", brokerHost, "with TLS?", fmt.Sprintf("%v", secure))
 	logger.Log(0, "connecting to mq broker at", brokerHost, "with TLS?", fmt.Sprintf("%v", secure))
-	var client = mq.SetupMQTT(false) // Set up the subscription listener
+	mq.SetupMQTT()
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx, cancel := context.WithCancel(context.Background())
 	go mq.Keepalive(ctx)
 	go mq.Keepalive(ctx)
 	go logic.ManageZombies(ctx)
 	go logic.ManageZombies(ctx)
@@ -180,7 +180,6 @@ func runMessageQueue(wg *sync.WaitGroup) {
 	<-quit
 	<-quit
 	cancel()
 	cancel()
 	logger.Log(0, "Message Queue shutting down")
 	logger.Log(0, "Message Queue shutting down")
-	client.Disconnect(250)
 }
 }
 
 
 func setVerbosity() {
 func setVerbosity() {

+ 5 - 0
models/error.go

@@ -0,0 +1,5 @@
+package models
+
+type Error string
+
+func (e Error) Error() string { return string(e) }

+ 17 - 2
models/node.go

@@ -70,8 +70,8 @@ type Node struct {
 	IsRelay                 string               `json:"isrelay" bson:"isrelay" yaml:"isrelay" validate:"checkyesorno"`
 	IsRelay                 string               `json:"isrelay" bson:"isrelay" yaml:"isrelay" validate:"checkyesorno"`
 	IsDocker                string               `json:"isdocker" bson:"isdocker" yaml:"isdocker" validate:"checkyesorno"`
 	IsDocker                string               `json:"isdocker" bson:"isdocker" yaml:"isdocker" validate:"checkyesorno"`
 	IsK8S                   string               `json:"isk8s" bson:"isk8s" yaml:"isk8s" validate:"checkyesorno"`
 	IsK8S                   string               `json:"isk8s" bson:"isk8s" yaml:"isk8s" validate:"checkyesorno"`
-	IsEgressGateway         string               `json:"isegressgateway" bson:"isegressgateway" yaml:"isegressgateway"`
-	IsIngressGateway        string               `json:"isingressgateway" bson:"isingressgateway" yaml:"isingressgateway"`
+	IsEgressGateway         string               `json:"isegressgateway" bson:"isegressgateway" yaml:"isegressgateway" validate:"checkyesorno"`
+	IsIngressGateway        string               `json:"isingressgateway" bson:"isingressgateway" yaml:"isingressgateway" validate:"checkyesorno"`
 	EgressGatewayRanges     []string             `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	EgressGatewayRanges     []string             `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	EgressGatewayNatEnabled string               `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"`
 	EgressGatewayNatEnabled string               `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"`
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
@@ -93,6 +93,7 @@ type Node struct {
 	TrafficKeys     TrafficKeys `json:"traffickeys" bson:"traffickeys" yaml:"traffickeys"`
 	TrafficKeys     TrafficKeys `json:"traffickeys" bson:"traffickeys" yaml:"traffickeys"`
 	FirewallInUse   string      `json:"firewallinuse" bson:"firewallinuse" yaml:"firewallinuse"`
 	FirewallInUse   string      `json:"firewallinuse" bson:"firewallinuse" yaml:"firewallinuse"`
 	InternetGateway string      `json:"internetgateway" bson:"internetgateway" yaml:"internetgateway"`
 	InternetGateway string      `json:"internetgateway" bson:"internetgateway" yaml:"internetgateway"`
+	Connected       string      `json:"connected" bson:"connected" yaml:"connected" validate:"checkyesorno"`
 }
 }
 
 
 // NodesArray - used for node sorting
 // NodesArray - used for node sorting
@@ -121,6 +122,16 @@ func (node *Node) PrimaryAddress() string {
 	return node.Address6
 	return node.Address6
 }
 }
 
 
+// Node.SetDefaultConnected
+func (node *Node) SetDefaultConnected() {
+	if node.Connected == "" {
+		node.Connected = "yes"
+	}
+	if node.IsServer == "yes" {
+		node.Connected = "yes"
+	}
+}
+
 // Node.SetDefaultMTU - sets default MTU of a node
 // Node.SetDefaultMTU - sets default MTU of a node
 func (node *Node) SetDefaultMTU() {
 func (node *Node) SetDefaultMTU() {
 	if node.MTU == 0 {
 	if node.MTU == 0 {
@@ -382,6 +393,7 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable
 	}
 	}
 	if newNode.IsServer == "yes" {
 	if newNode.IsServer == "yes" {
 		newNode.IsStatic = "yes"
 		newNode.IsStatic = "yes"
+		newNode.Connected = "yes"
 	}
 	}
 	if newNode.MTU == 0 {
 	if newNode.MTU == 0 {
 		newNode.MTU = currentNode.MTU
 		newNode.MTU = currentNode.MTU
@@ -413,6 +425,9 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable
 	if newNode.Server == "" {
 	if newNode.Server == "" {
 		newNode.Server = currentNode.Server
 		newNode.Server = currentNode.Server
 	}
 	}
+	if newNode.Connected == "" {
+		newNode.Connected = currentNode.Connected
+	}
 	newNode.TrafficKeys = currentNode.TrafficKeys
 	newNode.TrafficKeys = currentNode.TrafficKeys
 }
 }
 
 

+ 5 - 0
models/structs.go

@@ -10,6 +10,11 @@ import (
 const PLACEHOLDER_KEY_TEXT = "ACCESS_KEY"
 const PLACEHOLDER_KEY_TEXT = "ACCESS_KEY"
 const PLACEHOLDER_TOKEN_TEXT = "ACCESS_TOKEN"
 const PLACEHOLDER_TOKEN_TEXT = "ACCESS_TOKEN"
 
 
+// CustomExtClient - struct for CustomExtClient params
+type CustomExtClient struct {
+	ClientID string `json:"clientid"`
+}
+
 // AuthParams - struct for auth params
 // AuthParams - struct for auth params
 type AuthParams struct {
 type AuthParams struct {
 	MacAddress string `json:"macaddress"`
 	MacAddress string `json:"macaddress"`

+ 19 - 23
mq/mq.go

@@ -21,8 +21,10 @@ const MQ_TIMEOUT = 30
 
 
 var peer_force_send = 0
 var peer_force_send = 0
 
 
+var mqclient mqtt.Client
+
 // SetupMQTT creates a connection to broker and return client
 // SetupMQTT creates a connection to broker and return client
-func SetupMQTT(publish bool) mqtt.Client {
+func SetupMQTT() {
 	opts := mqtt.NewClientOptions()
 	opts := mqtt.NewClientOptions()
 	broker, secure := servercfg.GetMessageQueueEndpoint()
 	broker, secure := servercfg.GetMessageQueueEndpoint()
 	opts.AddBroker(broker)
 	opts.AddBroker(broker)
@@ -37,28 +39,26 @@ func SetupMQTT(publish bool) mqtt.Client {
 	opts.SetKeepAlive(time.Minute)
 	opts.SetKeepAlive(time.Minute)
 	opts.SetWriteTimeout(time.Minute)
 	opts.SetWriteTimeout(time.Minute)
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
-		if !publish {
-			if token := client.Subscribe("ping/#", 2, mqtt.MessageHandler(Ping)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
-				client.Disconnect(240)
-				logger.Log(0, "ping subscription failed")
-			}
-			if token := client.Subscribe("update/#", 0, mqtt.MessageHandler(UpdateNode)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
-				client.Disconnect(240)
-				logger.Log(0, "node update subscription failed")
-			}
-			if token := client.Subscribe("signal/#", 0, mqtt.MessageHandler(ClientPeerUpdate)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
-				client.Disconnect(240)
-				logger.Log(0, "node client subscription failed")
-			}
-
-			opts.SetOrderMatters(true)
-			opts.SetResumeSubs(true)
+		if token := client.Subscribe("ping/#", 2, mqtt.MessageHandler(Ping)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
+			client.Disconnect(240)
+			logger.Log(0, "ping subscription failed")
 		}
 		}
+		if token := client.Subscribe("update/#", 0, mqtt.MessageHandler(UpdateNode)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
+			client.Disconnect(240)
+			logger.Log(0, "node update subscription failed")
+		}
+		if token := client.Subscribe("signal/#", 0, mqtt.MessageHandler(ClientPeerUpdate)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
+			client.Disconnect(240)
+			logger.Log(0, "node client subscription failed")
+		}
+
+		opts.SetOrderMatters(true)
+		opts.SetResumeSubs(true)
 	})
 	})
-	client := mqtt.NewClient(opts)
+	mqclient = mqtt.NewClient(opts)
 	tperiod := time.Now().Add(10 * time.Second)
 	tperiod := time.Now().Add(10 * time.Second)
 	for {
 	for {
-		if token := client.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
+		if token := mqclient.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
 			logger.Log(2, "unable to connect to broker, retrying ...")
 			logger.Log(2, "unable to connect to broker, retrying ...")
 			if time.Now().After(tperiod) {
 			if time.Now().After(tperiod) {
 				if token.Error() == nil {
 				if token.Error() == nil {
@@ -72,10 +72,6 @@ func SetupMQTT(publish bool) mqtt.Client {
 		}
 		}
 		time.Sleep(2 * time.Second)
 		time.Sleep(2 * time.Second)
 	}
 	}
-	if !publish {
-		logger.Log(0, "successfully connected to mq broker")
-	}
-	return client
 }
 }
 
 
 // Keepalive -- periodically pings all nodes to let them know server is still alive and doing well
 // Keepalive -- periodically pings all nodes to let them know server is still alive and doing well

+ 5 - 0
mq/publishers.go

@@ -85,6 +85,11 @@ func NodeUpdate(node *models.Node) error {
 		return nil
 		return nil
 	}
 	}
 	logger.Log(3, "publishing node update to "+node.Name)
 	logger.Log(3, "publishing node update to "+node.Name)
+
+	if len(node.NetworkSettings.AccessKeys) > 0 {
+		node.NetworkSettings.AccessKeys = []models.AccessKey{} // not to be sent (don't need to spread access keys around the network; we need to know how to reach other nodes, not become them)
+	}
+
 	data, err := json.Marshal(node)
 	data, err := json.Marshal(node)
 	if err != nil {
 	if err != nil {
 		logger.Log(2, "error marshalling node update ", err.Error())
 		logger.Log(2, "error marshalling node update ", err.Error())

+ 5 - 4
mq/util.go

@@ -61,13 +61,14 @@ func encryptMsg(node *models.Node, msg []byte) ([]byte, error) {
 }
 }
 
 
 func publish(node *models.Node, dest string, msg []byte) error {
 func publish(node *models.Node, dest string, msg []byte) error {
-	client := SetupMQTT(true)
-	defer client.Disconnect(250)
 	encrypted, encryptErr := encryptMsg(node, msg)
 	encrypted, encryptErr := encryptMsg(node, msg)
 	if encryptErr != nil {
 	if encryptErr != nil {
 		return encryptErr
 		return encryptErr
 	}
 	}
-	if token := client.Publish(dest, 0, true, encrypted); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
+	if mqclient == nil {
+		return errors.New("cannot publish ... mqclient not connected")
+	}
+	if token := mqclient.Publish(dest, 0, true, encrypted); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
 		var err error
 		var err error
 		if token.Error() == nil {
 		if token.Error() == nil {
 			err = errors.New("connection timeout")
 			err = errors.New("connection timeout")
@@ -79,7 +80,7 @@ func publish(node *models.Node, dest string, msg []byte) error {
 	return nil
 	return nil
 }
 }
 
 
-//  decodes a message queue topic and returns the embedded node.ID
+// decodes a message queue topic and returns the embedded node.ID
 func getID(topic string) (string, error) {
 func getID(topic string) (string, error) {
 	parts := strings.Split(topic, "/")
 	parts := strings.Split(topic, "/")
 	count := len(parts)
 	count := len(parts)

+ 26 - 0
netclient/cli_options/cmds.go

@@ -104,6 +104,32 @@ func GetCommands(cliFlags []cli.Flag) []*cli.Command {
 				return command.Install()
 				return command.Install()
 			},
 			},
 		},
 		},
+		{
+			Name:  "connect",
+			Usage: "connect netclient to a given network if disconnected",
+			Flags: cliFlags,
+			Action: func(c *cli.Context) error {
+				parseVerbosity(c)
+				cfg, _, err := config.GetCLIConfig(c)
+				if err != nil {
+					return err
+				}
+				return command.Connect(cfg)
+			},
+		},
+		{
+			Name:  "disconnect",
+			Usage: "disconnect netclient from a given network if connected",
+			Flags: cliFlags,
+			Action: func(c *cli.Context) error {
+				parseVerbosity(c)
+				cfg, _, err := config.GetCLIConfig(c)
+				if err != nil {
+					return err
+				}
+				return command.Disconnect(cfg)
+			},
+		},
 	}
 	}
 }
 }
 
 

+ 26 - 1
netclient/command/commands.go

@@ -3,6 +3,7 @@ package command
 import (
 import (
 	"crypto/ed25519"
 	"crypto/ed25519"
 	"crypto/rand"
 	"crypto/rand"
+	"fmt"
 	"strings"
 	"strings"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
@@ -21,7 +22,7 @@ func Join(cfg *config.ClientConfig, privateKey string) error {
 	if err != nil {
 	if err != nil {
 		if !strings.Contains(err.Error(), "ALREADY_INSTALLED") {
 		if !strings.Contains(err.Error(), "ALREADY_INSTALLED") {
 			logger.Log(0, "error installing: ", err.Error())
 			logger.Log(0, "error installing: ", err.Error())
-			err = functions.WipeLocal(cfg.Network)
+			err = functions.WipeLocal(cfg)
 			if err != nil {
 			if err != nil {
 				logger.Log(1, "error removing artifacts: ", err.Error())
 				logger.Log(1, "error removing artifacts: ", err.Error())
 			}
 			}
@@ -142,3 +143,27 @@ func Daemon() error {
 func Install() error {
 func Install() error {
 	return functions.Install()
 	return functions.Install()
 }
 }
+
+// Connect - re-instates a connection of a node
+func Connect(cfg config.ClientConfig) error {
+	networkName := cfg.Network
+	if networkName == "" {
+		networkName = cfg.Node.Network
+	}
+	if networkName == "all" {
+		return fmt.Errorf("no network specified")
+	}
+	return functions.Connect(networkName)
+}
+
+// Disconnect - disconnects a connection of a node
+func Disconnect(cfg config.ClientConfig) error {
+	networkName := cfg.Network
+	if networkName == "" {
+		networkName = cfg.Node.Network
+	}
+	if networkName == "all" {
+		return fmt.Errorf("no network specified")
+	}
+	return functions.Disconnect(networkName)
+}

+ 1 - 0
netclient/daemon/freebsd.go

@@ -108,6 +108,7 @@ func FreebsdDaemon(command string) {
 
 
 // CleanupFreebsd - removes config files and netclient binary
 // CleanupFreebsd - removes config files and netclient binary
 func CleanupFreebsd() {
 func CleanupFreebsd() {
+	ncutils.RunCmd("service netclient stop", false)
 	RemoveFreebsdDaemon()
 	RemoveFreebsdDaemon()
 	if err := os.RemoveAll(ncutils.GetNetclientPath()); err != nil {
 	if err := os.RemoveAll(ncutils.GetNetclientPath()); err != nil {
 		logger.Log(1, "Removing netclient configs: ", err.Error())
 		logger.Log(1, "Removing netclient configs: ", err.Error())

+ 3 - 0
netclient/daemon/systemd.go

@@ -83,6 +83,9 @@ func RestartSystemD() {
 
 
 // CleanupLinux - cleans up neclient configs
 // CleanupLinux - cleans up neclient configs
 func CleanupLinux() {
 func CleanupLinux() {
+	if _, err := ncutils.RunCmd("systemctl stop netclient", false); err != nil {
+		logger.Log(0, "failed to stop netclient service", err.Error())
+	}
 	RemoveSystemDServices()
 	RemoveSystemDServices()
 	if err := os.RemoveAll(ncutils.GetNetclientPath()); err != nil {
 	if err := os.RemoveAll(ncutils.GetNetclientPath()); err != nil {
 		logger.Log(1, "Removing netclient configs: ", err.Error())
 		logger.Log(1, "Removing netclient configs: ", err.Error())

+ 3 - 0
netclient/functions/clientconfig.go

@@ -43,6 +43,9 @@ func UpdateClientConfig() {
 			if err := PublishNodeUpdate(&cfg); err != nil {
 			if err := PublishNodeUpdate(&cfg); err != nil {
 				logger.Log(0, "error publishing node update during schema change", err.Error())
 				logger.Log(0, "error publishing node update during schema change", err.Error())
 			}
 			}
+			if err := config.ModNodeConfig(&cfg.Node); err != nil {
+				logger.Log(0, "error saving local config for node,", cfg.Node.Name, ", on network,", cfg.Node.Network)
+			}
 		}
 		}
 	}
 	}
 	logger.Log(0, "finished updates")
 	logger.Log(0, "finished updates")

+ 111 - 114
netclient/functions/common.go

@@ -10,6 +10,7 @@ import (
 	"net"
 	"net"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
+	"path/filepath"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
@@ -172,58 +173,100 @@ func LeaveNetwork(network string) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	node := cfg.Node
-	if node.IsServer != "yes" {
-		token, err := Authenticate(cfg)
-		if err != nil {
-			logger.Log(0, "network:", cfg.Network, "unable to authenticate: "+err.Error())
-		} else {
-			url := "https://" + cfg.Server.API + "/api/nodes/" + cfg.Network + "/" + cfg.Node.ID
-			response, err := API("", http.MethodDelete, url, token)
-			if err != nil {
-				logger.Log(0, "network:", cfg.Network, "error deleting node on server: "+err.Error())
-			} else {
-				if response.StatusCode == http.StatusOK {
-					logger.Log(0, "network:", cfg.Network, "deleted node", cfg.Node.Name, ".")
-				} else {
-					bodybytes, _ := io.ReadAll(response.Body)
-					defer response.Body.Close()
-					logger.Log(0, fmt.Sprintf("network: %s error deleting node on server %s %s", cfg.Network, response.Status, string(bodybytes)))
-				}
-			}
-		}
+	logger.Log(2, "deleting node from server")
+	if err := deleteNodeFromServer(cfg); err != nil {
+		logger.Log(0, "error deleting node from server", err.Error())
 	}
 	}
-	wgClient, wgErr := wgctrl.New()
-	if wgErr == nil {
-		removeIface := cfg.Node.Interface
-		queryAddr := cfg.Node.PrimaryAddress()
-		if ncutils.IsMac() {
-			var macIface string
-			macIface, wgErr = local.GetMacIface(queryAddr)
-			if wgErr == nil && removeIface != "" {
-				removeIface = macIface
-			}
+	logger.Log(2, "deleting wireguard interface")
+	if err := deleteLocalNetwork(cfg); err != nil {
+		logger.Log(0, "error deleting wireguard interface", err.Error())
+	}
+	logger.Log(2, "deleting configuration files")
+	if err := WipeLocal(cfg); err != nil {
+		logger.Log(0, "error deleting local network files", err.Error())
+	}
+	logger.Log(2, "removing dns entries")
+	if err := removeHostDNS(cfg.Node.Interface, ncutils.IsWindows()); err != nil {
+		logger.Log(0, "failed to delete dns entries for", cfg.Node.Interface, err.Error())
+	}
+	logger.Log(2, "deleting broker keys as required")
+	if !brokerInUse(cfg.Server.Server) {
+		if err := deleteBrokerFiles(cfg.Server.Server); err != nil {
+			logger.Log(0, "failed to deleter certs for", cfg.Server.Server, err.Error())
 		}
 		}
-		dev, devErr := wgClient.Device(removeIface)
-		if devErr == nil {
-			local.FlushPeerRoutes(removeIface, queryAddr, dev.Peers[:])
-			_, cidr, cidrErr := net.ParseCIDR(cfg.NetworkSettings.AddressRange)
-			if cidrErr == nil {
-				local.RemoveCIDRRoute(removeIface, queryAddr, cidr)
-			}
-		} else {
-			logger.Log(1, "could not flush peer routes when leaving network,", cfg.Node.Network)
+	}
+	logger.Log(2, "restarting daemon")
+	return daemon.Restart()
+}
+
+func brokerInUse(broker string) bool {
+	networks, _ := ncutils.GetSystemNetworks()
+	for _, net := range networks {
+		cfg := config.ClientConfig{}
+		cfg.Network = net
+		cfg.ReadConfig()
+		if cfg.Server.Server == broker {
+			return true
 		}
 		}
 	}
 	}
+	return false
+}
+
+func deleteBrokerFiles(broker string) error {
+	dir := ncutils.GetNetclientServerPath(broker)
+	if err := os.RemoveAll(dir); err != nil {
+		return err
+	}
+	return nil
+}
 
 
-	err = WipeLocal(node.Network)
+func deleteNodeFromServer(cfg *config.ClientConfig) error {
+	node := cfg.Node
+	if node.IsServer == "yes" {
+		return errors.New("attempt to delete server node ... not permitted")
+	}
+	token, err := Authenticate(cfg)
 	if err != nil {
 	if err != nil {
-		logger.Log(1, "network:", node.Network, "unable to wipe local config")
-	} else {
-		logger.Log(1, "removed", node.Network, "network locally")
+		return fmt.Errorf("unable to authenticate %w", err)
+	}
+	url := "https://" + cfg.Server.API + "/api/nodes/" + cfg.Network + "/" + cfg.Node.ID
+	response, err := API("", http.MethodDelete, url, token)
+	if err != nil {
+		return fmt.Errorf("error deleting node on server: %w", err)
 	}
 	}
+	if response.StatusCode != http.StatusOK {
+		bodybytes, _ := io.ReadAll(response.Body)
+		defer response.Body.Close()
+		return fmt.Errorf("error deleting node from network %s on server %s %s", cfg.Network, response.Status, string(bodybytes))
+	}
+	return nil
+}
 
 
-	return daemon.Restart()
+func deleteLocalNetwork(cfg *config.ClientConfig) error {
+	wgClient, wgErr := wgctrl.New()
+	if wgErr != nil {
+		return wgErr
+	}
+	removeIface := cfg.Node.Interface
+	queryAddr := cfg.Node.PrimaryAddress()
+	if ncutils.IsMac() {
+		var macIface string
+		macIface, wgErr = local.GetMacIface(queryAddr)
+		if wgErr == nil && removeIface != "" {
+			removeIface = macIface
+		}
+	}
+	dev, devErr := wgClient.Device(removeIface)
+	if devErr != nil {
+		return fmt.Errorf("error flushing routes %w", devErr)
+	}
+	local.FlushPeerRoutes(removeIface, queryAddr, dev.Peers[:])
+	_, cidr, cidrErr := net.ParseCIDR(cfg.NetworkSettings.AddressRange)
+	if cidrErr != nil {
+		return fmt.Errorf("error flushing routes %w", cidrErr)
+	}
+	local.RemoveCIDRRoute(removeIface, queryAddr, cidr)
+	return nil
 }
 }
 
 
 // DeleteInterface - delete an interface of a network
 // DeleteInterface - delete an interface of a network
@@ -232,85 +275,39 @@ func DeleteInterface(ifacename string, postdown string) error {
 }
 }
 
 
 // WipeLocal - wipes local instance
 // WipeLocal - wipes local instance
-func WipeLocal(network string) error {
-	var ifacename string
-
-	if network == "" {
-		return errors.New("no network provided")
+func WipeLocal(cfg *config.ClientConfig) error {
+	if err := wireguard.RemoveConf(cfg.Node.Interface, true); err == nil {
+		logger.Log(1, "network:", cfg.Node.Network, "removed WireGuard interface: ", cfg.Node.Interface)
+	} else if strings.Contains(err.Error(), "does not exist") {
+		err = nil
+	}
+	dir := ncutils.GetNetclientPathSpecific()
+	fail := false
+	files, err := filepath.Glob(dir + "*" + cfg.Node.Network)
+	if err != nil {
+		logger.Log(0, "no matching files", err.Error())
+		fail = true
 	}
 	}
-	cfg, err := config.ReadConfig(network)
-	if err == nil {
-		nodecfg := cfg.Node
-		ifacename = nodecfg.Interface
-		if ifacename != "" {
-			if err = wireguard.RemoveConf(ifacename, true); err == nil {
-				logger.Log(1, "network:", nodecfg.Network, "removed WireGuard interface: ", ifacename)
-			} else if strings.Contains(err.Error(), "does not exist") {
-				err = nil
-			}
+	for _, file := range files {
+		if err := os.Remove(file); err != nil {
+			logger.Log(0, "failed to delete file", file, err.Error())
+			fail = true
 		}
 		}
-	} else {
-		logger.Log(0, "failed to read "+network+" config: ", err.Error())
 	}
 	}
 
 
-	home := ncutils.GetNetclientPathSpecific()
-	if ncutils.FileExists(home + "netconfig-" + network) {
-		err = os.Remove(home + "netconfig-" + network)
-		if err != nil {
-			log.Println("error removing netconfig:")
-			log.Println(err.Error())
-		}
-	}
-	if ncutils.FileExists(home + "backup.netconfig-" + network) {
-		err = os.Remove(home + "backup.netconfig-" + network)
-		if err != nil {
-			log.Println("error removing backup netconfig:")
-			log.Println(err.Error())
-		}
-	}
-	if ncutils.FileExists(home + "nettoken-" + network) {
-		err = os.Remove(home + "nettoken-" + network)
-		if err != nil {
-			log.Println("error removing nettoken:")
-			log.Println(err.Error())
-		}
-	}
-	if ncutils.FileExists(home + "secret-" + network) {
-		err = os.Remove(home + "secret-" + network)
-		if err != nil {
-			log.Println("error removing secret:")
-			log.Println(err.Error())
-		}
-	}
-	if ncutils.FileExists(home + "traffic-" + network) {
-		err = os.Remove(home + "traffic-" + network)
-		if err != nil {
-			log.Println("error removing traffic key:")
-			log.Println(err.Error())
-		}
-	}
-	if ncutils.FileExists(home + "wgkey-" + network) {
-		err = os.Remove(home + "wgkey-" + network)
-		if err != nil {
-			log.Println("error removing wgkey:")
-			log.Println(err.Error())
-		}
-	}
-	if ifacename != "" {
-		if ncutils.FileExists(home + ifacename + ".conf") {
-			err = os.Remove(home + ifacename + ".conf")
-			if err != nil {
+	if cfg.Node.Interface != "" {
+		if ncutils.FileExists(dir + cfg.Node.Interface + ".conf") {
+			if err := os.Remove(dir + cfg.Node.Interface + ".conf"); err != nil {
 				log.Println("error removing .conf:")
 				log.Println("error removing .conf:")
 				log.Println(err.Error())
 				log.Println(err.Error())
+				fail = true
 			}
 			}
 		}
 		}
-		err = removeHostDNS(ifacename, ncutils.IsWindows())
-		if err != nil {
-			logger.Log(0, "failed to delete dns entries for", ifacename, err.Error())
-		}
 	}
 	}
-
-	return err
+	if fail {
+		return errors.New("not all files were deleted")
+	}
+	return nil
 }
 }
 
 
 // GetNetmakerPath - gets netmaker path locally
 // GetNetmakerPath - gets netmaker path locally

+ 54 - 0
netclient/functions/connection.go

@@ -0,0 +1,54 @@
+package functions
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/netclient/wireguard"
+)
+
+// Connect - will attempt to connect a node on given network
+func Connect(network string) error {
+	cfg, err := config.ReadConfig(network)
+	if err != nil {
+		return err
+	}
+	if cfg.Node.Connected == "yes" {
+		return fmt.Errorf("node already connected")
+	}
+	cfg.Node.Connected = "yes"
+	filePath := ncutils.GetNetclientPathSpecific() + cfg.Node.Interface + ".conf"
+
+	if err = wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, filePath); err != nil {
+		return err
+	}
+	if err := PublishNodeUpdate(cfg); err != nil {
+		logger.Log(0, "network:", cfg.Node.Network, "could not publish connection change, it will likely get reverted")
+	}
+
+	return config.ModNodeConfig(&cfg.Node)
+}
+
+// Disconnect - attempts to disconnect a node on given network
+func Disconnect(network string) error {
+	cfg, err := config.ReadConfig(network)
+	if err != nil {
+		return err
+	}
+	if cfg.Node.Connected == "no" {
+		return fmt.Errorf("node already disconnected")
+	}
+	cfg.Node.Connected = "no"
+	filePath := ncutils.GetNetclientPathSpecific() + cfg.Node.Interface + ".conf"
+
+	if err = wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, filePath); err != nil {
+		return err
+	}
+	if err := PublishNodeUpdate(cfg); err != nil {
+		logger.Log(0, "network:", cfg.Node.Network, "could not publish connection change, it will likely get reverted")
+	}
+
+	return config.ModNodeConfig(&cfg.Node)
+}

+ 20 - 20
netclient/functions/daemon.go

@@ -34,6 +34,8 @@ var messageCache = new(sync.Map)
 
 
 var serverSet map[string]bool
 var serverSet map[string]bool
 
 
+var mqclient mqtt.Client
+
 const lastNodeUpdate = "lnu"
 const lastNodeUpdate = "lnu"
 const lastPeerUpdate = "lpu"
 const lastPeerUpdate = "lpu"
 
 
@@ -192,12 +194,12 @@ func unsubscribeNode(client mqtt.Client, nodeCfg *config.ClientConfig) {
 func messageQueue(ctx context.Context, wg *sync.WaitGroup, cfg *config.ClientConfig) {
 func messageQueue(ctx context.Context, wg *sync.WaitGroup, cfg *config.ClientConfig) {
 	defer wg.Done()
 	defer wg.Done()
 	logger.Log(0, "network:", cfg.Node.Network, "netclient message queue started for server:", cfg.Server.Server)
 	logger.Log(0, "network:", cfg.Node.Network, "netclient message queue started for server:", cfg.Server.Server)
-	client, err := setupMQTT(cfg, false)
+	err := setupMQTT(cfg)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "unable to connect to broker", cfg.Server.Server, err.Error())
 		logger.Log(0, "unable to connect to broker", cfg.Server.Server, err.Error())
 		return
 		return
 	}
 	}
-	defer client.Disconnect(250)
+	//defer mqclient.Disconnect(250)
 	<-ctx.Done()
 	<-ctx.Done()
 	logger.Log(0, "shutting down message queue for server", cfg.Server.Server)
 	logger.Log(0, "shutting down message queue for server", cfg.Server.Server)
 }
 }
@@ -232,7 +234,7 @@ func NewTLSConfig(server string) (*tls.Config, error) {
 
 
 // setupMQTT creates a connection to broker and returns client
 // setupMQTT creates a connection to broker and returns client
 // this function is primarily used to create a connection to publish to the broker
 // this function is primarily used to create a connection to publish to the broker
-func setupMQTT(cfg *config.ClientConfig, publish bool) (mqtt.Client, error) {
+func setupMQTT(cfg *config.ClientConfig) error {
 	opts := mqtt.NewClientOptions()
 	opts := mqtt.NewClientOptions()
 	server := cfg.Server.Server
 	server := cfg.Server.Server
 	port := cfg.Server.MQPort
 	port := cfg.Server.MQPort
@@ -240,7 +242,7 @@ func setupMQTT(cfg *config.ClientConfig, publish bool) (mqtt.Client, error) {
 	tlsConfig, err := NewTLSConfig(server)
 	tlsConfig, err := NewTLSConfig(server)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "failed to get TLS config for", server, err.Error())
 		logger.Log(0, "failed to get TLS config for", server, err.Error())
-		return nil, err
+		return err
 	}
 	}
 	opts.SetTLSConfig(tlsConfig)
 	opts.SetTLSConfig(tlsConfig)
 	opts.SetClientID(ncutils.MakeRandomString(23))
 	opts.SetClientID(ncutils.MakeRandomString(23))
@@ -252,17 +254,15 @@ func setupMQTT(cfg *config.ClientConfig, publish bool) (mqtt.Client, error) {
 	opts.SetWriteTimeout(time.Minute)
 	opts.SetWriteTimeout(time.Minute)
 
 
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
-		if !publish {
-			networks, err := ncutils.GetSystemNetworks()
-			if err != nil {
-				logger.Log(0, "error retriving networks", err.Error())
-			}
-			for _, network := range networks {
-				var currNodeCfg config.ClientConfig
-				currNodeCfg.Network = network
-				currNodeCfg.ReadConfig()
-				setSubscriptions(client, &currNodeCfg)
-			}
+		networks, err := ncutils.GetSystemNetworks()
+		if err != nil {
+			logger.Log(0, "error retriving networks", err.Error())
+		}
+		for _, network := range networks {
+			var currNodeCfg config.ClientConfig
+			currNodeCfg.Network = network
+			currNodeCfg.ReadConfig()
+			setSubscriptions(client, &currNodeCfg)
 		}
 		}
 	})
 	})
 	opts.SetOrderMatters(true)
 	opts.SetOrderMatters(true)
@@ -270,11 +270,11 @@ func setupMQTT(cfg *config.ClientConfig, publish bool) (mqtt.Client, error) {
 	opts.SetConnectionLostHandler(func(c mqtt.Client, e error) {
 	opts.SetConnectionLostHandler(func(c mqtt.Client, e error) {
 		logger.Log(0, "network:", cfg.Node.Network, "detected broker connection lost for", cfg.Server.Server)
 		logger.Log(0, "network:", cfg.Node.Network, "detected broker connection lost for", cfg.Server.Server)
 	})
 	})
-	client := mqtt.NewClient(opts)
+	mqclient = mqtt.NewClient(opts)
 	var connecterr error
 	var connecterr error
 	for count := 0; count < 3; count++ {
 	for count := 0; count < 3; count++ {
 		connecterr = nil
 		connecterr = nil
-		if token := client.Connect(); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
+		if token := mqclient.Connect(); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
 			logger.Log(0, "unable to connect to broker, retrying ...")
 			logger.Log(0, "unable to connect to broker, retrying ...")
 			if token.Error() == nil {
 			if token.Error() == nil {
 				connecterr = errors.New("connect timeout")
 				connecterr = errors.New("connect timeout")
@@ -289,12 +289,12 @@ func setupMQTT(cfg *config.ClientConfig, publish bool) (mqtt.Client, error) {
 	if connecterr != nil {
 	if connecterr != nil {
 		reRegisterWithServer(cfg)
 		reRegisterWithServer(cfg)
 		//try after re-registering
 		//try after re-registering
-		if token := client.Connect(); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
-			return client, errors.New("unable to connect to broker")
+		if token := mqclient.Connect(); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
+			return errors.New("unable to connect to broker")
 		}
 		}
 	}
 	}
 
 
-	return client, nil
+	return nil
 }
 }
 
 
 func reRegisterWithServer(cfg *config.ClientConfig) {
 func reRegisterWithServer(cfg *config.ClientConfig) {

+ 1 - 6
netclient/functions/join.go

@@ -211,18 +211,13 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 	}
 	}
 
 
 	logger.Log(0, "starting wireguard")
 	logger.Log(0, "starting wireguard")
-	err = wireguard.InitWireguard(&node, privateKey, nodeGET.Peers[:], false)
+	err = wireguard.InitWireguard(&node, privateKey, nodeGET.Peers[:])
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	if cfg.Server.Server == "" {
 	if cfg.Server.Server == "" {
 		return errors.New("did not receive broker address from registration")
 		return errors.New("did not receive broker address from registration")
 	}
 	}
-	// update server with latest data
-	if err := PublishNodeUpdate(cfg); err != nil {
-		logger.Log(0, "network:", cfg.Network, "failed to publish update for join", err.Error())
-	}
-
 	if cfg.Daemon == "install" || ncutils.IsFreeBSD() {
 	if cfg.Daemon == "install" || ncutils.IsFreeBSD() {
 		err = daemon.InstallDaemon()
 		err = daemon.InstallDaemon()
 		if err != nil {
 		if err != nil {

+ 1 - 1
netclient/functions/localport.go

@@ -44,7 +44,7 @@ func UpdateLocalListenPort(nodeCfg *config.ClientConfig) error {
 			return err
 			return err
 		}
 		}
 		if err := PublishNodeUpdate(nodeCfg); err != nil {
 		if err := PublishNodeUpdate(nodeCfg); err != nil {
-			logger.Log(0, "could not publish local port change")
+			logger.Log(0, "could not publish local port change", err.Error())
 		}
 		}
 	}
 	}
 	return err
 	return err

+ 34 - 33
netclient/functions/mqhandlers.go

@@ -109,44 +109,45 @@ func NodeUpdate(client mqtt.Client, msg mqtt.Message) {
 	}
 	}
 	file := ncutils.GetNetclientPathSpecific() + nodeCfg.Node.Interface + ".conf"
 	file := ncutils.GetNetclientPathSpecific() + nodeCfg.Node.Interface + ".conf"
 
 
-	if ifaceDelta { // if a change caused an ifacedelta we need to notify the server to update the peers
-		if newNode.ListenPort != nodeCfg.Node.LocalListenPort {
-			if err := wireguard.RemoveConf(newNode.Interface, false); err != nil {
-				logger.Log(0, "error remove interface", newNode.Interface, err.Error())
-			}
-			err = ncutils.ModPort(&newNode)
-			if err != nil {
-				logger.Log(0, "network:", nodeCfg.Node.Network, "error modifying node port on", newNode.Name, "-", err.Error())
-				return
-			}
-			informPortChange(&newNode)
-		}
-		if err := wireguard.UpdateWgInterface(file, privateKey, nameserver, newNode); err != nil {
-			logger.Log(0, "error updating wireguard config "+err.Error())
-			return
-		}
-		if keepaliveChange {
-			wireguard.UpdateKeepAlive(file, newNode.PersistentKeepalive)
+	if newNode.ListenPort != nodeCfg.Node.LocalListenPort {
+		if err := wireguard.RemoveConf(newNode.Interface, false); err != nil {
+			logger.Log(0, "error remove interface", newNode.Interface, err.Error())
 		}
 		}
-		logger.Log(0, "applying WG conf to "+file)
-		if ncutils.IsWindows() {
-			wireguard.RemoveConfGraceful(nodeCfg.Node.Interface)
-		}
-		err = wireguard.ApplyConf(&nodeCfg.Node, nodeCfg.Node.Interface, file)
+		err = ncutils.ModPort(&newNode)
 		if err != nil {
 		if err != nil {
-			logger.Log(0, "error restarting wg after node update -", err.Error())
+			logger.Log(0, "network:", nodeCfg.Node.Network, "error modifying node port on", newNode.Name, "-", err.Error())
 			return
 			return
 		}
 		}
+		ifaceDelta = true
+		informPortChange(&newNode)
+	}
+	if err := wireguard.UpdateWgInterface(file, privateKey, nameserver, newNode); err != nil {
+		logger.Log(0, "error updating wireguard config "+err.Error())
+		return
+	}
+	if keepaliveChange {
+		wireguard.UpdateKeepAlive(file, newNode.PersistentKeepalive)
+	}
+	logger.Log(0, "applying WG conf to "+file)
+	if ncutils.IsWindows() {
+		wireguard.RemoveConfGraceful(nodeCfg.Node.Interface)
+	}
+	err = wireguard.ApplyConf(&nodeCfg.Node, nodeCfg.Node.Interface, file)
+	if err != nil {
+		logger.Log(0, "error restarting wg after node update -", err.Error())
+		return
+	}
 
 
-		time.Sleep(time.Second)
-		//	if newNode.DNSOn == "yes" {
-		//		for _, server := range newNode.NetworkSettings.DefaultServerAddrs {
-		//			if server.IsLeader {
-		//				go local.SetDNSWithRetry(newNode, server.Address)
-		//				break
-		//			}
-		//		}
-		//	}
+	time.Sleep(time.Second)
+	//	if newNode.DNSOn == "yes" {
+	//		for _, server := range newNode.NetworkSettings.DefaultServerAddrs {
+	//			if server.IsLeader {
+	//				go local.SetDNSWithRetry(newNode, server.Address)
+	//				break
+	//			}
+	//		}
+	//	}
+	if ifaceDelta { // if a change caused an ifacedelta we need to notify the server to update the peers
 		doneErr := publishSignal(&nodeCfg, ncutils.DONE)
 		doneErr := publishSignal(&nodeCfg, ncutils.DONE)
 		if doneErr != nil {
 		if doneErr != nil {
 			logger.Log(0, "network:", nodeCfg.Node.Network, "could not notify server to update peers after interface change")
 			logger.Log(0, "network:", nodeCfg.Node.Network, "could not notify server to update peers after interface change")

+ 11 - 8
netclient/functions/mqpublish.go

@@ -21,7 +21,8 @@ import (
 )
 )
 
 
 // Checkin  -- go routine that checks for public or local ip changes, publishes changes
 // Checkin  -- go routine that checks for public or local ip changes, publishes changes
-//   if there are no updates, simply "pings" the server as a checkin
+//
+//	if there are no updates, simply "pings" the server as a checkin
 func Checkin(ctx context.Context, wg *sync.WaitGroup) {
 func Checkin(ctx context.Context, wg *sync.WaitGroup) {
 	logger.Log(2, "starting checkin goroutine")
 	logger.Log(2, "starting checkin goroutine")
 	defer wg.Done()
 	defer wg.Done()
@@ -93,6 +94,11 @@ func checkin() {
 				}
 				}
 			}
 			}
 		}
 		}
+		//check version
+		if nodeCfg.Node.Version != ncutils.Version {
+			nodeCfg.Node.Version = ncutils.Version
+			config.Write(&nodeCfg, nodeCfg.Network)
+		}
 		Hello(&nodeCfg)
 		Hello(&nodeCfg)
 		checkCertExpiry(&nodeCfg)
 		checkCertExpiry(&nodeCfg)
 	}
 	}
@@ -141,17 +147,14 @@ func publish(nodeCfg *config.ClientConfig, dest string, msg []byte, qos byte) er
 		return err
 		return err
 	}
 	}
 
 
-	client, err := setupMQTT(nodeCfg, true)
-	if err != nil {
-		return fmt.Errorf("mq setup error %w", err)
-	}
-	defer client.Disconnect(250)
 	encrypted, err := ncutils.Chunk(msg, serverPubKey, trafficPrivKey)
 	encrypted, err := ncutils.Chunk(msg, serverPubKey, trafficPrivKey)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-
-	if token := client.Publish(dest, qos, false, encrypted); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
+	if mqclient == nil {
+		return errors.New("unable to publish ... no mqclient")
+	}
+	if token := mqclient.Publish(dest, qos, false, encrypted); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
 		logger.Log(0, "could not connect to broker at "+nodeCfg.Server.Server+":"+nodeCfg.Server.MQPort)
 		logger.Log(0, "could not connect to broker at "+nodeCfg.Server.Server+":"+nodeCfg.Server.MQPort)
 		var err error
 		var err error
 		if token.Error() == nil {
 		if token.Error() == nil {

+ 1 - 0
netclient/functions/upgrades/upgrades.go

@@ -4,6 +4,7 @@ func init() {
 	addUpgrades([]UpgradeInfo{
 	addUpgrades([]UpgradeInfo{
 		upgrade0145,
 		upgrade0145,
 		upgrade0146,
 		upgrade0146,
+		upgrade0148,
 	})
 	})
 }
 }
 
 

+ 22 - 0
netclient/functions/upgrades/v0-14-8.go

@@ -0,0 +1,22 @@
+package upgrades
+
+import (
+	"github.com/gravitl/netmaker/netclient/config"
+)
+
+var upgrade0148 = UpgradeInfo{
+	RequiredVersions: []string{
+		"v0.14.5",
+		"v0.14.6",
+		"v0.14.7",
+	},
+	NewVersion: "v0.14.8",
+	OP:         update0148,
+}
+
+func update0148(cfg *config.ClientConfig) {
+	// set connect default if not present 14.X -> 14.8
+	if cfg.Node.Connected == "" {
+		cfg.Node.SetDefaultConnected()
+	}
+}

+ 3 - 0
netclient/local/routes.go

@@ -106,6 +106,9 @@ func FlushPeerRoutes(iface, currentAddr string, peers []wgtypes.Peer) {
 		for _, allowedIP := range peer.AllowedIPs {
 		for _, allowedIP := range peer.AllowedIPs {
 			deleteRoute(iface, &allowedIP, currentAddr)
 			deleteRoute(iface, &allowedIP, currentAddr)
 		}
 		}
+		if peer.Endpoint == nil {
+			continue
+		}
 		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
 		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
 			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
 			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
 			if err != nil {
 			if err != nil {

+ 3 - 0
netclient/ncutils/iface.go

@@ -22,6 +22,9 @@ func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 		newNode.IsPending != currentNode.IsPending ||
 		newNode.IsPending != currentNode.IsPending ||
 		newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
 		newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
 		newNode.DNSOn != currentNode.DNSOn ||
 		newNode.DNSOn != currentNode.DNSOn ||
+		newNode.Connected != currentNode.Connected ||
+		newNode.PostUp != currentNode.PostUp ||
+		newNode.PostDown != currentNode.PostDown ||
 		len(newNode.AllowedIPs) != len(currentNode.AllowedIPs) {
 		len(newNode.AllowedIPs) != len(currentNode.AllowedIPs) {
 		return true
 		return true
 	}
 	}

+ 9 - 13
netclient/wireguard/common.go

@@ -123,7 +123,7 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 }
 }
 
 
 // Initializes a WireGuard interface
 // Initializes a WireGuard interface
-func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, syncconf bool) error {
+func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig) error {
 
 
 	key, err := wgtypes.ParseKey(privkey)
 	key, err := wgtypes.ParseKey(privkey)
 	if err != nil {
 	if err != nil {
@@ -191,10 +191,7 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 		}
 		}
 	}
 	}
 	logger.Log(1, "interface ready - netclient.. ENGAGE")
 	logger.Log(1, "interface ready - netclient.. ENGAGE")
-	if syncconf { // should never be called really.
-		fmt.Println("why here")
-		err = SyncWGQuickConf(ifacename, confPath)
-	}
+
 	if !ncutils.HasWgQuick() && ncutils.IsLinux() {
 	if !ncutils.HasWgQuick() && ncutils.IsLinux() {
 		err = SetPeers(ifacename, node, peers)
 		err = SetPeers(ifacename, node, peers)
 		if err != nil {
 		if err != nil {
@@ -248,12 +245,9 @@ func SetWGConfig(network string, peerupdate bool, peers []wgtypes.PeerConfig) er
 			}
 			}
 		}
 		}
 		err = SetPeers(iface, &cfg.Node, peers)
 		err = SetPeers(iface, &cfg.Node, peers)
-	} else if peerupdate {
-		err = InitWireguard(&cfg.Node, privkey, peers, true)
 	} else {
 	} else {
-		err = InitWireguard(&cfg.Node, privkey, peers, false)
+		err = InitWireguard(&cfg.Node, privkey, peers)
 	}
 	}
-
 	return err
 	return err
 }
 }
 
 
@@ -284,16 +278,17 @@ func ApplyConf(node *models.Node, ifacename string, confPath string) error {
 	if ncutils.IsLinux() && !ncutils.HasWgQuick() {
 	if ncutils.IsLinux() && !ncutils.HasWgQuick() {
 		os = "nowgquick"
 		os = "nowgquick"
 	}
 	}
+	var isConnected = node.Connected != "no"
 	var err error
 	var err error
 	switch os {
 	switch os {
 	case "windows":
 	case "windows":
-		ApplyWindowsConf(confPath)
+		ApplyWindowsConf(confPath, isConnected)
 	case "darwin":
 	case "darwin":
-		ApplyMacOSConf(node, ifacename, confPath)
+		ApplyMacOSConf(node, ifacename, confPath, isConnected)
 	case "nowgquick":
 	case "nowgquick":
-		ApplyWithoutWGQuick(node, ifacename, confPath)
+		ApplyWithoutWGQuick(node, ifacename, confPath, isConnected)
 	default:
 	default:
-		ApplyWGQuickConf(confPath, ifacename)
+		ApplyWGQuickConf(confPath, ifacename, isConnected)
 	}
 	}
 
 
 	var nodeCfg config.ClientConfig
 	var nodeCfg config.ClientConfig
@@ -448,6 +443,7 @@ func UpdateWgInterface(file, privateKey, nameserver string, node models.Node) er
 	if node.UDPHolePunch == "yes" {
 	if node.UDPHolePunch == "yes" {
 		node.ListenPort = 0
 		node.ListenPort = 0
 	}
 	}
+	wireguard.DeleteSection(section_interface)
 	wireguard.Section(section_interface).Key("PrivateKey").SetValue(privateKey)
 	wireguard.Section(section_interface).Key("PrivateKey").SetValue(privateKey)
 	wireguard.Section(section_interface).Key("ListenPort").SetValue(strconv.Itoa(int(node.ListenPort)))
 	wireguard.Section(section_interface).Key("ListenPort").SetValue(strconv.Itoa(int(node.ListenPort)))
 	addrString := node.Address
 	addrString := node.Address

+ 15 - 3
netclient/wireguard/noquick.go

@@ -2,6 +2,7 @@ package wireguard
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"fmt"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"strconv"
 	"strconv"
@@ -15,8 +16,10 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
+const disconnect_error = "node disconnected"
+
 // ApplyWithoutWGQuick - Function for running the equivalent of "wg-quick up" for linux if wg-quick is missing
 // ApplyWithoutWGQuick - Function for running the equivalent of "wg-quick up" for linux if wg-quick is missing
-func ApplyWithoutWGQuick(node *models.Node, ifacename string, confPath string) error {
+func ApplyWithoutWGQuick(node *models.Node, ifacename, confPath string, isConnected bool) error {
 
 
 	ipExec, err := exec.LookPath("ip")
 	ipExec, err := exec.LookPath("ip")
 	if err != nil {
 	if err != nil {
@@ -72,7 +75,12 @@ func ApplyWithoutWGQuick(node *models.Node, ifacename string, confPath string) e
 		mask6 = netmask
 		mask6 = netmask
 		address6 = node.Address6
 		address6 = node.Address6
 	}
 	}
-	setKernelDevice(ifacename, address4, mask4, address6, mask6)
+	err = setKernelDevice(ifacename, address4, mask4, address6, mask6, isConnected)
+	if err != nil {
+		if err.Error() == disconnect_error {
+			return nil
+		}
+	}
 
 
 	_, err = wgclient.Device(ifacename)
 	_, err = wgclient.Device(ifacename)
 	if err != nil {
 	if err != nil {
@@ -140,7 +148,7 @@ func RemoveWithoutWGQuick(ifacename string) error {
 	return err
 	return err
 }
 }
 
 
-func setKernelDevice(ifacename, address4, mask4, address6, mask6 string) error {
+func setKernelDevice(ifacename, address4, mask4, address6, mask6 string, isConnected bool) error {
 	ipExec, err := exec.LookPath("ip")
 	ipExec, err := exec.LookPath("ip")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -148,6 +156,10 @@ func setKernelDevice(ifacename, address4, mask4, address6, mask6 string) error {
 
 
 	// == best effort ==
 	// == best effort ==
 	ncutils.RunCmd("ip link delete dev "+ifacename, false)
 	ncutils.RunCmd("ip link delete dev "+ifacename, false)
+	if !isConnected {
+		return fmt.Errorf(disconnect_error)
+	}
+
 	ncutils.RunCmd(ipExec+" link add dev "+ifacename+" type wireguard", true)
 	ncutils.RunCmd(ipExec+" link add dev "+ifacename+" type wireguard", true)
 	if address4 != "" {
 	if address4 != "" {
 		ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+address4+"/"+mask4, true)
 		ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+address4+"/"+mask4, true)

+ 9 - 35
netclient/wireguard/unix.go

@@ -2,9 +2,7 @@ package wireguard
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"log"
 	"os"
 	"os"
-	"regexp"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
@@ -12,9 +10,9 @@ import (
 )
 )
 
 
 // ApplyWGQuickConf - applies wg-quick commands if os supports
 // ApplyWGQuickConf - applies wg-quick commands if os supports
-func ApplyWGQuickConf(confPath string, ifacename string) error {
+func ApplyWGQuickConf(confPath, ifacename string, isConnected bool) error {
 	if ncutils.IsWindows() {
 	if ncutils.IsWindows() {
-		return ApplyWindowsConf(confPath)
+		return ApplyWindowsConf(confPath, isConnected)
 	} else {
 	} else {
 		_, err := os.Stat(confPath)
 		_, err := os.Stat(confPath)
 		if err != nil {
 		if err != nil {
@@ -24,6 +22,9 @@ func ApplyWGQuickConf(confPath string, ifacename string) error {
 		if ncutils.IfaceExists(ifacename) {
 		if ncutils.IfaceExists(ifacename) {
 			ncutils.RunCmd("wg-quick down "+confPath, true)
 			ncutils.RunCmd("wg-quick down "+confPath, true)
 		}
 		}
+		if !isConnected {
+			return nil
+		}
 		_, err = ncutils.RunCmd("wg-quick up "+confPath, true)
 		_, err = ncutils.RunCmd("wg-quick up "+confPath, true)
 
 
 		return err
 		return err
@@ -31,40 +32,13 @@ func ApplyWGQuickConf(confPath string, ifacename string) error {
 }
 }
 
 
 // ApplyMacOSConf - applies system commands similar to wg-quick using golang for MacOS
 // ApplyMacOSConf - applies system commands similar to wg-quick using golang for MacOS
-func ApplyMacOSConf(node *models.Node, ifacename string, confPath string) error {
+func ApplyMacOSConf(node *models.Node, ifacename, confPath string, isConnected bool) error {
 	var err error
 	var err error
 	_ = WgQuickDownMac(node, ifacename)
 	_ = WgQuickDownMac(node, ifacename)
-	err = WgQuickUpMac(node, ifacename, confPath)
-	return err
-}
-
-// SyncWGQuickConf - formats config file and runs sync command
-func SyncWGQuickConf(iface string, confPath string) error {
-	var tmpConf = confPath + ".sync.tmp"
-	var confCmd = "wg-quick strip "
-	if ncutils.IsMac() {
-		confCmd = "grep -v -e Address -e MTU -e PostUp -e PostDown "
-	}
-	confRaw, err := ncutils.RunCmd(confCmd+confPath, false)
-	if err != nil {
-		return err
-	}
-	regex := regexp.MustCompile(".*Warning.*\n")
-	conf := regex.ReplaceAllString(confRaw, "")
-	err = os.WriteFile(tmpConf, []byte(conf), 0600)
-	if err != nil {
-		return err
-	}
-	_, err = ncutils.RunCmd("wg syncconf "+iface+" "+tmpConf, true)
-	if err != nil {
-		log.Println(err.Error())
-		logger.Log(0, "error syncing conf, resetting")
-		err = ApplyWGQuickConf(confPath, iface)
-	}
-	errN := os.Remove(tmpConf)
-	if errN != nil {
-		logger.Log(0, errN.Error())
+	if !isConnected {
+		return nil
 	}
 	}
+	err = WgQuickUpMac(node, ifacename, confPath)
 	return err
 	return err
 }
 }
 
 

+ 4 - 1
netclient/wireguard/windows.go

@@ -8,7 +8,10 @@ import (
 )
 )
 
 
 // ApplyWindowsConf - applies the WireGuard configuration file on Windows
 // ApplyWindowsConf - applies the WireGuard configuration file on Windows
-func ApplyWindowsConf(confPath string) error {
+func ApplyWindowsConf(confPath string, isConnected bool) error {
+	if !isConnected {
+		return nil
+	}
 	var commandLine = fmt.Sprintf(`wireguard.exe /installtunnelservice "%s"`, confPath)
 	var commandLine = fmt.Sprintf(`wireguard.exe /installtunnelservice "%s"`, confPath)
 	if _, err := ncutils.RunCmdFormatted(commandLine, false); err != nil {
 	if _, err := ncutils.RunCmdFormatted(commandLine, false); err != nil {
 		return err
 		return err

+ 1 - 1
scripts/nm-quick-interactive.sh

@@ -247,7 +247,7 @@ SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
 
 
 EGRESS_JSON=$( jq -n \
 EGRESS_JSON=$( jq -n \
                   --arg gw "$GATEWAY_IFACE" \
                   --arg gw "$GATEWAY_IFACE" \
-                  '{ranges: ["0.0.0.0/5","8.0.0.0/7","11.0.0.0/8","12.0.0.0/6","16.0.0.0/4","32.0.0.0/3","64.0.0.0/2","128.0.0.0/3","160.0.0.0/5","168.0.0.0/6","172.0.0.0/12","172.32.0.0/11","172.64.0.0/10","172.128.0.0/9","173.0.0.0/8","174.0.0.0/7","176.0.0.0/4","192.0.0.0/9","192.128.0.0/11","192.160.0.0/13","192.169.0.0/16","192.170.0.0/15","192.172.0.0/14","192.176.0.0/12","192.192.0.0/10","193.0.0.0/8","194.0.0.0/7","196.0.0.0/6","200.0.0.0/5","208.0.0.0/4"], interface: $gw}' )
+                  '{ranges: ["0.0.0.0/0","::/0"], interface: $gw}' )
 
 
 echo "Egress json: $EGRESS_JSON"
 echo "Egress json: $EGRESS_JSON"
 curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/creategateway
 curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/creategateway

+ 1 - 1
scripts/nm-quick.sh

@@ -257,7 +257,7 @@ SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
 
 
 EGRESS_JSON=$( jq -n \
 EGRESS_JSON=$( jq -n \
                   --arg gw "$GATEWAY_IFACE" \
                   --arg gw "$GATEWAY_IFACE" \
-                  '{ranges: ["0.0.0.0/5","8.0.0.0/7","11.0.0.0/8","12.0.0.0/6","16.0.0.0/4","32.0.0.0/3","64.0.0.0/2","128.0.0.0/3","160.0.0.0/5","168.0.0.0/6","172.0.0.0/12","172.32.0.0/11","172.64.0.0/10","172.128.0.0/9","173.0.0.0/8","174.0.0.0/7","176.0.0.0/4","192.0.0.0/9","192.128.0.0/11","192.160.0.0/13","192.169.0.0/16","192.170.0.0/15","192.172.0.0/14","192.176.0.0/12","192.192.0.0/10","193.0.0.0/8","194.0.0.0/7","196.0.0.0/6","200.0.0.0/5","208.0.0.0/4"], interface: $gw}' )
+                  '{ranges: ["0.0.0.0/0","::/0"], interface: $gw}' )
 
 
 echo "egress json: $EGRESS_JSON"
 echo "egress json: $EGRESS_JSON"
 curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/creategateway
 curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/creategateway

+ 12 - 2
serverctl/serverctl.go

@@ -81,14 +81,24 @@ func SyncServerNetwork(network string) error {
 	return nil
 	return nil
 }
 }
 
 
-// SetDefaultACLS - runs through each network to see if ACL's are set. If not, goes through each node in network and adds the default ACL
-func SetDefaultACLS() error {
+func SetDefaults() error {
+	if err := setNodeDefaults(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// setNodeDefaults - runs through each node and set defaults
+func setNodeDefaults() error {
 	// upgraded systems will not have ACL's set, which is why we need this function
 	// upgraded systems will not have ACL's set, which is why we need this function
 	nodes, err := logic.GetAllNodes()
 	nodes, err := logic.GetAllNodes()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	for i := range nodes {
 	for i := range nodes {
+		logic.SetNodeDefaults(&nodes[i])
+		logic.UpdateNode(&nodes[i], &nodes[i])
 		currentNodeACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(nodes[i].Network), nodeacls.NodeID(nodes[i].ID))
 		currentNodeACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(nodes[i].Network), nodeacls.NodeID(nodes[i].ID))
 		if (err != nil && (database.IsEmptyRecord(err) || strings.Contains(err.Error(), "no node ACL present"))) || currentNodeACL == nil {
 		if (err != nil && (database.IsEmptyRecord(err) || strings.Contains(err.Error(), "no node ACL present"))) || currentNodeACL == nil {
 			if _, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(nodes[i].Network), nodeacls.NodeID(nodes[i].ID), acls.Allowed); err != nil {
 			if _, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(nodes[i].Network), nodeacls.NodeID(nodes[i].ID), acls.Allowed); err != nil {