Browse Source

Merge remote-tracking branch 'origin/develop' into GRA-1375-default-proxy

Matthew R Kasun 2 years ago
parent
commit
8b5d194666

+ 17 - 0
compose/docker-compose.netclient.yml

@@ -0,0 +1,17 @@
+version: "3.4"
+
+services:
+  netclient:
+    container_name: netclient
+    image: 'gravitl/netclient:v0.18.3'
+    hostname: netmaker-1
+    network_mode: host
+    restart: always
+    environment:
+      TOKEN: "TOKEN_VALUE"
+    volumes:
+      - /etc/netclient:/etc/netclient
+    cap_add:
+      - NET_ADMIN
+      - NET_RAW
+      - SYS_MODULE

+ 12 - 4
controllers/ext_client.go

@@ -363,6 +363,13 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 	}
 
+	if err := logic.SetClientDefaultACLs(&extclient); err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to assign ACLs to new ext client on network [%s]: %v", networkName, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
 	if err = logic.CreateExtClient(&extclient); err != nil {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to create new ext client on network [%s]: %v", networkName, err))
@@ -384,7 +391,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 				logger.Log(0, "failed to associate client", extclient.ClientID, "to user", userID)
 			}
 			extclient.OwnerID = userID
-			if _, err := logic.UpdateExtClient(extclient.ClientID, extclient.Network, extclient.Enabled, &extclient); err != nil {
+			if _, err := logic.UpdateExtClient(extclient.ClientID, extclient.Network, extclient.Enabled, &extclient, extclient.ACLs); err != nil {
 				logger.Log(0, "failed to add owner id", userID, "to client", extclient.ClientID)
 			}
 		}
@@ -477,10 +484,11 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	// == END PRO ==
 
-	var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement
+	var changedEnabled = (newExtClient.Enabled != oldExtClient.Enabled) || // indicates there was a change in enablement
+		len(newExtClient.ACLs) != len(oldExtClient.ACLs)
 	// extra var need as logic.Update changes oldExtClient
 	currentClient := oldExtClient
-	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient)
+	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient, newExtClient.ACLs)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to update ext client [%s], network [%s]: %v",
@@ -570,7 +578,7 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 
 	go func() {
-		if err := mq.PublishPeerUpdate(); err != nil {
+		if err := mq.PublishDeletedClientPeerUpdate(&extclient); err != nil {
 			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
 		}
 		if err = mq.PublishDeleteExtClientDNS(&extclient); err != nil {

+ 46 - 63
controllers/migrate.go

@@ -2,19 +2,17 @@ package controller
 
 import (
 	"encoding/json"
-	"io"
 	"net/http"
-	"strings"
 
-	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 )
 
-// swagger:route PUT /api/nodes/{network}/{nodeid}/migrate nodes migrateNode
+// swagger:route PUT /api/v1/nodes/migrate nodes migrateNode
 //
 // Used to migrate a legacy node.
 //
@@ -26,7 +24,6 @@ import (
 //			Responses:
 //				200: nodeJoinResponse
 func migrate(w http.ResponseWriter, r *http.Request) {
-	// we decode our body request params
 	data := models.MigrationData{}
 	err := json.NewDecoder(r.Body).Decode(&data)
 	if err != nil {
@@ -34,70 +31,56 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	params := mux.Vars(r)
-	//check authorization
-	record, err := database.FetchRecord(database.NODES_TABLE_NAME, data.LegacyNodeID)
-	if err != nil {
-		logger.Log(0, "no record for legacy node", data.LegacyNodeID, err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	var legacyNode models.LegacyNode
-	if err = json.Unmarshal([]byte(record), &legacyNode); err != nil {
-		logger.Log(0, "error decoding legacy node", err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
+
+	var networksToAdd = []string{}
+	for i := range data.LegacyNodes {
+		legacyNode := data.LegacyNodes[i]
+		record, err := database.FetchRecord(database.NODES_TABLE_NAME, legacyNode.ID)
+		if err != nil {
+			logger.Log(0, "no record for legacy node", legacyNode.ID, err.Error())
+			continue
+		} else {
+			var oldLegacyNode models.LegacyNode
+			if err = json.Unmarshal([]byte(record), &oldLegacyNode); err != nil {
+				logger.Log(0, "error decoding legacy node", err.Error())
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+				continue
+			}
+			if err := bcrypt.CompareHashAndPassword([]byte(oldLegacyNode.Password), []byte(legacyNode.Password)); err != nil {
+				logger.Log(0, "error decoding legacy password", err.Error())
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "unauthorized"))
+				continue
+			}
+			networksToAdd = append(networksToAdd, oldLegacyNode.Network)
+			_ = database.DeleteRecord(database.NODES_TABLE_NAME, oldLegacyNode.ID)
+		}
 	}
-	if err := bcrypt.CompareHashAndPassword([]byte(legacyNode.Password), []byte(data.Password)); err != nil {
-		logger.Log(0, "error decoding legacy password", err.Error())
+	if len(networksToAdd) == 0 {
+		logger.Log(0, "no valid networks to migrate for host", data.NewHost.Name)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "unauthorized"))
 		return
 	}
-	network, err := logic.GetNetwork(params["network"])
-	if err != nil {
-		logger.Log(0, "error retrieving network:  ", err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	key, err := logic.CreateAccessKey(models.AccessKey{}, network)
-	if err != nil {
-		logger.Log(0, "error creating key:  ", err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	data.JoinData.Key = key.Value
-	payload, err := json.Marshal(data.JoinData)
-	if err != nil {
-		logger.Log(0, "error encoding data:  ", err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	r.Body = io.NopCloser(strings.NewReader(string(payload)))
-	r.ContentLength = int64(len(string(payload)))
-	logger.Log(3, "deleteing legacy node", data.LegacyNodeID, legacyNode.ID, legacyNode.Name)
-	if err := database.DeleteRecord(database.NODES_TABLE_NAME, data.LegacyNodeID); err != nil {
-		logger.Log(0, "error deleting legacy node", legacyNode.Name, err.Error())
-	}
-	createNode(w, r)
-	//newly created node has same node id as legacy node allowing using legacyNode.ID in gateway creation
-	logger.Log(3, "re-creating legacy gateways")
-	if legacyNode.IsIngressGateway == "yes" {
-		if _, err := logic.CreateIngressGateway(legacyNode.Network, legacyNode.ID, false); err != nil {
-			logger.Log(0, "error creating ingress gateway during migration", err.Error())
+	if !logic.HostExists(&data.NewHost) {
+		if err = logic.CreateHost(&data.NewHost); err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
 		}
 	}
-	if legacyNode.IsEgressGateway == "yes" {
-		if _, err := logic.CreateEgressGateway(legacyNode.EgressGatewayRequest); err != nil {
-			logger.Log(0, "error creating egress gateway during migration", err.Error())
-		}
+	key, keyErr := logic.RetrievePublicTrafficKey()
+	if keyErr != nil {
+		logger.Log(0, "error retrieving key:", keyErr.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
 	}
-	if legacyNode.IsRelay == "yes" {
-		if _, _, err := logic.CreateRelay(models.RelayRequest{
-			NodeID:     legacyNode.ID,
-			NetID:      legacyNode.Network,
-			RelayAddrs: legacyNode.RelayAddrs,
-		}); err != nil {
-			logger.Log(0, "error creating relay during migration", err.Error())
-		}
+	server := servercfg.GetServerInfo()
+	server.TrafficKey = key
+	response := models.RegisterResponse{
+		ServerConf:    server,
+		RequestedHost: data.NewHost,
 	}
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(&response)
+	logger.Log(0, "successfully migrated host", data.NewHost.Name, data.NewHost.ID.String())
+	// notify host of changes, peer and node updates
+	go checkNetRegAndHostUpdate(networksToAdd, &data.NewHost)
 }

+ 3 - 4
controllers/network_test.go

@@ -40,6 +40,8 @@ func TestMain(m *testing.M) {
 			logger.Log(3, "received node update", update.Action)
 		}
 	}()
+	os.Exit(m.Run())
+
 }
 
 func TestCreateNetwork(t *testing.T) {
@@ -325,10 +327,7 @@ func TestIpv6Network(t *testing.T) {
 
 func deleteAllNetworks() {
 	deleteAllNodes()
-	nets, _ := logic.GetNetworks()
-	for _, net := range nets {
-		logic.DeleteNetwork(net.NetID)
-	}
+	database.DeleteAllRecords(database.NETWORKS_TABLE_NAME)
 }
 
 func createNet() {

+ 3 - 3
controllers/node.go

@@ -26,7 +26,6 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}", authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/migrate", migrate).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
@@ -37,6 +36,7 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}", nodeauth(checkFreeTierLimits(node_l, http.HandlerFunc(createNode)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
 
 // swagger:route POST /api/nodes/adm/{network}/authenticate nodes authenticate
@@ -434,7 +434,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), node.Network, host, nil)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), node.Network, host, nil, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
@@ -623,7 +623,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), networkName, &data.Host, nil)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), networkName, &data.Host, nil, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", data.Host.ID.String(), err))

+ 2 - 2
controllers/node_test.go

@@ -113,12 +113,12 @@ func TestGetNetworkNodes(t *testing.T) {
 	t.Run("BadNet", func(t *testing.T) {
 		node, err := logic.GetNetworkNodes("badnet")
 		assert.Nil(t, err)
-		assert.Nil(t, node)
+		assert.Equal(t, []models.Node{}, node)
 	})
 	t.Run("NoNodes", func(t *testing.T) {
 		node, err := logic.GetNetworkNodes("skynet")
 		assert.Nil(t, err)
-		assert.Nil(t, node)
+		assert.Equal(t, []models.Node{}, node)
 	})
 	t.Run("Success", func(t *testing.T) {
 		createTestNode()

+ 4 - 0
controllers/user_test.go

@@ -186,6 +186,7 @@ func TestGetUsers(t *testing.T) {
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 	})
 	t.Run("UserExisits", func(t *testing.T) {
+		user.UserName = "anotheruser"
 		if err := logic.CreateUser(&adminUser); err != nil {
 			t.Error(err)
 		}
@@ -281,6 +282,9 @@ func TestVerifyAuthRequest(t *testing.T) {
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 	})
 	t.Run("Non-Admin", func(t *testing.T) {
+		user.IsAdmin = false
+		user.Password = "somepass"
+		user.UserName = "nonadmin"
 		if err := logic.CreateUser(&user); err != nil {
 			t.Error(err)
 		}

+ 3 - 0
ee/initialize.go

@@ -40,6 +40,9 @@ func InitEE() {
 	logic.EnterpriseFailoverFunc = eelogic.SetFailover
 	logic.EnterpriseResetFailoverFunc = eelogic.ResetFailover
 	logic.EnterpriseResetAllPeersFailovers = eelogic.WipeAffectedFailoversOnly
+	logic.DenyClientNodeAccess = eelogic.DenyClientNode
+	logic.IsClientNodeAllowed = eelogic.IsClientNodeAllowed
+	logic.AllowClientNodeAccess = eelogic.RemoveDeniedNodeFromClient
 }
 
 func setControllerLimits() {

+ 41 - 0
ee/logic/ext_acls.go

@@ -0,0 +1,41 @@
+package logic
+
+import "github.com/gravitl/netmaker/models"
+
+// DenyClientNode - add a denied node to an ext client's list
+func DenyClientNode(ec *models.ExtClient, clientOrNodeID string) (ok bool) {
+	if ec == nil || len(clientOrNodeID) == 0 {
+		return
+	}
+	if ec.ACLs == nil {
+		ec.ACLs = map[string]struct{}{}
+	}
+	ok = true
+	ec.ACLs[clientOrNodeID] = struct{}{}
+	return
+}
+
+// IsClientNodeAllowed - checks if given ext client and node are allowed to communicate
+func IsClientNodeAllowed(ec *models.ExtClient, clientOrNodeID string) bool {
+	if ec == nil || len(clientOrNodeID) == 0 {
+		return false
+	}
+	if ec.ACLs == nil {
+		return true
+	}
+	_, ok := ec.ACLs[clientOrNodeID]
+	return ok
+}
+
+// RemoveDeniedNodeFromClient - removes a node id from set of denied nodes
+func RemoveDeniedNodeFromClient(ec *models.ExtClient, clientOrNodeID string) bool {
+	if ec.ACLs == nil {
+		return true
+	}
+	_, ok := ec.ACLs[clientOrNodeID]
+	if !ok {
+		return false
+	}
+	delete(ec.ACLs, clientOrNodeID)
+	return true
+}

+ 12 - 21
functions/helpers_test.go

@@ -3,12 +3,14 @@ package functions
 import (
 	"context"
 	"encoding/json"
+	"os"
 	"testing"
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
+	"github.com/stretchr/testify/assert"
 )
 
 var (
@@ -39,40 +41,29 @@ func TestMain(m *testing.M) {
 			logger.Log(3, "received node update", update.Action)
 		}
 	}()
+	os.Exit(m.Run())
+
 }
 
 func TestNetworkExists(t *testing.T) {
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
-	defer database.CloseDB()
 	exists, err := logic.NetworkExists(testNetwork.NetID)
-	if err == nil {
-		t.Fatalf("expected error, received nil")
-	}
-	if exists {
-		t.Fatalf("expected false")
-	}
+	assert.NotNil(t, err)
+	assert.False(t, exists)
 
 	err = logic.SaveNetwork(testNetwork)
-	if err != nil {
-		t.Fatalf("failed to save test network in databse: %s", err)
-	}
+	assert.Nil(t, err)
 	exists, err = logic.NetworkExists(testNetwork.NetID)
-	if err != nil {
-		t.Fatalf("expected nil, received err: %s", err)
-	}
-	if !exists {
-		t.Fatalf("expected network to exist in database")
-	}
+	assert.Nil(t, err)
+	assert.True(t, exists)
 
 	err = database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
-	if err != nil {
-		t.Fatalf("expected nil, failed to delete test network: %s", err)
-	}
+	assert.Nil(t, err)
 }
 
 func TestGetAllExtClients(t *testing.T) {
-	defer database.CloseDB()
-	database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
+	err := database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
+	assert.Nil(t, err)
 
 	extClients, err := GetAllExtClients()
 	if err == nil {

+ 7 - 9
go.mod

@@ -15,11 +15,11 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.2
 	github.com/txn2/txeh v1.3.0
-	golang.org/x/crypto v0.6.0
-	golang.org/x/net v0.6.0 // indirect
-	golang.org/x/oauth2 v0.5.0
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/crypto v0.7.0
+	golang.org/x/net v0.8.0 // indirect
+	golang.org/x/oauth2 v0.6.0
+	golang.org/x/sys v0.6.0 // indirect
+	golang.org/x/text v0.8.0 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -42,8 +42,7 @@ require (
 
 require (
 	github.com/guumaster/tablewriter v0.0.10
-	github.com/kr/pretty v0.3.0
-	github.com/matryer/is v1.4.0
+	github.com/matryer/is v1.4.1
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/spf13/cobra v1.6.1
 )
@@ -52,9 +51,8 @@ require (
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.1 // indirect
-	github.com/kr/text v0.2.0 // indirect
+	github.com/kr/pretty v0.3.1 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
-	github.com/rogpeppe/go-internal v1.8.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 )
 

+ 16 - 18
go.sum

@@ -69,8 +69,8 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
 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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -80,8 +80,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
-github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
+github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
@@ -111,9 +111,8 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -156,8 +155,8 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -173,11 +172,11 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
-golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
-golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
+golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
+golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -199,8 +198,8 @@ golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -210,8 +209,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -235,7 +234,6 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

+ 53 - 0
logic/clients.go

@@ -0,0 +1,53 @@
+package logic
+
+import "github.com/gravitl/netmaker/models"
+
+// functions defined here, handle client ACLs, should be set on ee
+
+var (
+	// DenyClientNodeAccess - function to handle adding a node to an ext client's denied node set
+	DenyClientNodeAccess = func(ec *models.ExtClient, clientOrNodeID string) bool { return true }
+	// IsClientNodeAllowed - function to check if an ext client's denied node set contains a node ID
+	IsClientNodeAllowed = func(ec *models.ExtClient, clientOrNodeID string) bool { return true }
+	// AllowClientNodeAccess - function to handle removing a node ID from ext client's denied nodes, thus allowing it
+	AllowClientNodeAccess = func(ec *models.ExtClient, clientOrNodeID string) bool { return true }
+)
+
+// SetClientDefaultACLs - set's a client's default ACLs based on network and nodes in network
+func SetClientDefaultACLs(ec *models.ExtClient) error {
+	if !isEE {
+		return nil
+	}
+	networkNodes, err := GetNetworkNodes(ec.Network)
+	if err != nil {
+		return err
+	}
+	network, err := GetNetwork(ec.Network)
+	if err != nil {
+		return err
+	}
+	for i := range networkNodes {
+		currNode := networkNodes[i]
+		if network.DefaultACL == "no" || currNode.DefaultACL == "no" {
+			DenyClientNodeAccess(ec, currNode.ID.String())
+		}
+	}
+	return nil
+}
+
+// SetClientACLs - overwrites an ext client's ACL
+func SetClientACLs(ec *models.ExtClient, newACLs map[string]struct{}) {
+	if ec == nil || newACLs == nil || !isEE {
+		return
+	}
+	ec.ACLs = newACLs
+}
+
+// IsClientNodeAllowedByID - checks if a given ext client ID + nodeID are allowed
+func IsClientNodeAllowedByID(clientID, networkName, clientOrNodeID string) bool {
+	client, err := GetExtClient(clientID, networkName)
+	if err != nil {
+		return false
+	}
+	return IsClientNodeAllowed(&client, clientOrNodeID)
+}

+ 22 - 3
logic/extpeers.go

@@ -2,6 +2,7 @@ package logic
 
 import (
 	"encoding/json"
+	"fmt"
 	"time"
 
 	"github.com/gravitl/netmaker/database"
@@ -114,6 +115,22 @@ func GetExtClient(clientid string, network string) (models.ExtClient, error) {
 	return extclient, err
 }
 
+// GetExtClient - gets a single ext client on a network
+func GetExtClientByPubKey(publicKey string, network string) (*models.ExtClient, error) {
+	netClients, err := GetNetworkExtClients(network)
+	if err != nil {
+		return nil, err
+	}
+	for i := range netClients {
+		ec := netClients[i]
+		if ec.PublicKey == publicKey {
+			return &ec, nil
+		}
+	}
+
+	return nil, fmt.Errorf("no client found")
+}
+
 // CreateExtClient - creates an extclient
 func CreateExtClient(extclient *models.ExtClient) error {
 
@@ -172,15 +189,17 @@ func CreateExtClient(extclient *models.ExtClient) error {
 }
 
 // UpdateExtClient - only supports name changes right now
-func UpdateExtClient(newclientid string, network string, enabled bool, client *models.ExtClient) (*models.ExtClient, error) {
-
+func UpdateExtClient(newclientid string, network string, enabled bool, client *models.ExtClient, newACLs map[string]struct{}) (*models.ExtClient, error) {
 	err := DeleteExtClient(network, client.ClientID)
 	if err != nil {
 		return client, err
 	}
 	client.ClientID = newclientid
 	client.Enabled = enabled
-	CreateExtClient(client)
+	SetClientACLs(client, newACLs)
+	if err = CreateExtClient(client); err != nil {
+		return client, err
+	}
 	return client, err
 }
 

+ 20 - 1
logic/host_test.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"os"
 	"testing"
 
 	"github.com/google/uuid"
@@ -12,7 +13,7 @@ import (
 	"github.com/matryer/is"
 )
 
-func TestCheckPorts(t *testing.T) {
+func TestMain(m *testing.M) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	peerUpdate := make(chan *models.Node)
@@ -24,6 +25,10 @@ func TestCheckPorts(t *testing.T) {
 		}
 	}()
 
+	os.Exit(m.Run())
+}
+
+func TestCheckPorts(t *testing.T) {
 	h := models.Host{
 		ID:              uuid.New(),
 		EndpointIP:      net.ParseIP("192.168.1.1"),
@@ -36,10 +41,16 @@ func TestCheckPorts(t *testing.T) {
 		ListenPort:      51830,
 		ProxyListenPort: 51730,
 	}
+	//not sure why this initialization is required but without it
+	// RemoveHost returns database is closed
+	database.InitializeDatabase()
+	RemoveHost(&h)
 	CreateHost(&h)
 	t.Run("no change", func(t *testing.T) {
 		is := is.New(t)
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, 51830)
 		is.Equal(testHost.ProxyListenPort, 51730)
 	})
@@ -47,6 +58,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		testHost.ListenPort = 51821
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, 51822)
 		is.Equal(testHost.ProxyListenPort, 51730)
 	})
@@ -54,6 +67,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		testHost.ProxyListenPort = 65535
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, 51822)
 		is.Equal(testHost.ProxyListenPort, minPort)
 	})
@@ -61,6 +76,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		testHost.ListenPort = maxPort
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, minPort)
 		is.Equal(testHost.ProxyListenPort, minPort+1)
 	})
@@ -68,6 +85,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		testHost.ProxyListenPort = 51821
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, minPort)
 		is.Equal(testHost.ProxyListenPort, 51822)
 	})

+ 2 - 1
logic/metrics/metrics.go

@@ -10,7 +10,7 @@ import (
 )
 
 // Collect - collects metrics
-func Collect(iface, server, network string, peerMap models.PeerMap) (*models.Metrics, error) {
+func Collect(iface, server, network string, peerMap models.PeerMap, proxy bool) (*models.Metrics, error) {
 	var metrics models.Metrics
 	metrics.Connectivity = make(map[string]models.Metric)
 	var wgclient, err = wgctrl.New()
@@ -45,6 +45,7 @@ func Collect(iface, server, network string, peerMap models.PeerMap) (*models.Met
 		newMetric.TotalSent = int64(proxyMetrics.TrafficSent)
 		newMetric.Latency = int64(proxyMetrics.LastRecordedLatency)
 		newMetric.Connected = proxyMetrics.NodeConnectionStatus[id]
+		newMetric.CollectedByProxy = proxy
 		if newMetric.Connected {
 			newMetric.Uptime = 1
 		}

+ 64 - 11
logic/peers.go

@@ -47,7 +47,7 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 		relayPeersMap := make(map[string]models.RelayedConf)
 		for _, relayedHost := range relayedHosts {
 			relayedHost := relayedHost
-			payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil)
+			payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil, nil)
 			if err == nil {
 				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
 				if udpErr == nil {
@@ -90,6 +90,7 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 				currPeerConf = models.PeerConf{
 					Proxy:            peerHost.ProxyEnabled,
 					PublicListenPort: int32(GetPeerListenPort(peerHost)),
+					ProxyListenPort:  GetProxyListenPort(peerHost),
 				}
 			}
 
@@ -133,7 +134,7 @@ func ResetPeerUpdateContext() {
 }
 
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
-func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
+func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host, deletedNode *models.Node, deletedClient *models.ExtClient) (models.HostPeerUpdate, error) {
 	if host == nil {
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 	}
@@ -250,6 +251,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 									},
 									PeerKey: extPeerIdAndAddr.ID,
 									Allow:   true,
+									ID:      extPeerIdAndAddr.ID,
 								}
 							}
 						}
@@ -265,6 +267,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 						},
 						PeerKey: peerHost.PublicKey.String(),
 						Allow:   true,
+						ID:      peer.ID.String(),
 					}
 				}
 
@@ -274,10 +277,11 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
 					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
-						ID:      peer.ID.String(),
-						Address: peer.PrimaryAddress(),
-						Name:    peerHost.Name,
-						Network: peer.Network,
+						ID:              peer.ID.String(),
+						Address:         peer.PrimaryAddress(),
+						Name:            peerHost.Name,
+						Network:         peer.Network,
+						ProxyListenPort: GetProxyListenPort(peerHost),
 					}
 					nodePeer = peerConfig
 				} else {
@@ -285,10 +289,11 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 					peerAllowedIPs = append(peerAllowedIPs, allowedips...)
 					hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
-						ID:      peer.ID.String(),
-						Address: peer.PrimaryAddress(),
-						Name:    peerHost.Name,
-						Network: peer.Network,
+						ID:              peer.ID.String(),
+						Address:         peer.PrimaryAddress(),
+						Name:            peerHost.Name,
+						Network:         peer.Network,
+						ProxyListenPort: GetProxyListenPort(peerHost),
 					}
 					nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
 				}
@@ -319,6 +324,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 							},
 							PeerKey: extPeerIdAndAddr.ID,
 							Allow:   true,
+							ID:      extPeerIdAndAddr.ID,
 						}
 					}
 					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
@@ -331,6 +337,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 							Name:    extPeerIdAndAddr.Name,
 							Network: node.Network,
 						}
+
 						hostPeerUpdate.IngressInfo.ExtPeers[extPeerIdAndAddr.ID] = models.ExtClientInfo{
 							Masquerade: true,
 							IngGwAddr: net.IPNet{
@@ -343,7 +350,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 								Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
 							},
 							ExtPeerKey: extPeerIdAndAddr.ID,
-							Peers:      nodePeerMap,
+							Peers:      filterNodeMapForClientACLs(extPeerIdAndAddr.ID, node.Network, nodePeerMap),
 						}
 						if node.Network == network {
 							hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
@@ -386,6 +393,16 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 		hostPeerUpdate.NodePeers[i] = peer
 	}
 
+	if deletedClient != nil {
+		key, err := wgtypes.ParseKey(deletedClient.PublicKey)
+		if err == nil {
+			hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, wgtypes.PeerConfig{
+				PublicKey: key,
+				Remove:    true,
+			})
+		}
+	}
+
 	return hostPeerUpdate, nil
 }
 
@@ -402,6 +419,15 @@ func GetPeerListenPort(host *models.Host) int {
 	return peerPort
 }
 
+// GetProxyListenPort - fetches the proxy listen port
+func GetProxyListenPort(host *models.Host) int {
+	proxyPort := host.ProxyListenPort
+	if host.PublicListenPort != 0 {
+		proxyPort = host.PublicListenPort
+	}
+	return proxyPort
+}
+
 func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, error) {
 	var peers []wgtypes.PeerConfig
 	var idsAndAddr []models.IDandAddr
@@ -414,6 +440,7 @@ func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, e
 		return peers, idsAndAddr, err
 	}
 	for _, extPeer := range extPeers {
+		extPeer := extPeer
 		pubkey, err := wgtypes.ParseKey(extPeer.PublicKey)
 		if err != nil {
 			logger.Log(1, "error parsing ext pub key:", err.Error())
@@ -645,3 +672,29 @@ func getCIDRMaskFromAddr(addr string) net.IPMask {
 	}
 	return cidr
 }
+
+// accounts for ext client ACLs
+func filterNodeMapForClientACLs(publicKey, network string, nodePeerMap map[string]models.PeerRouteInfo) map[string]models.PeerRouteInfo {
+	if !isEE {
+		return nodePeerMap
+	}
+	if nodePeerMap == nil {
+		return map[string]models.PeerRouteInfo{}
+	}
+
+	if len(publicKey) == 0 || len(network) == 0 {
+		return nodePeerMap
+	}
+
+	client, err := GetExtClientByPubKey(publicKey, network)
+	if err != nil {
+		return nodePeerMap
+	}
+	for k := range nodePeerMap {
+		currNodePeer := nodePeerMap[k]
+		if _, ok := client.ACLs[currNodePeer.ID]; ok {
+			delete(nodePeerMap, k)
+		}
+	}
+	return nodePeerMap
+}

+ 3 - 1
logic/pro/networkuser_test.go

@@ -1,6 +1,7 @@
 package pro
 
 import (
+	"os"
 	"testing"
 
 	"github.com/google/uuid"
@@ -13,6 +14,7 @@ import (
 func TestMain(m *testing.M) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
+	os.Exit(m.Run())
 }
 
 func TestNetworkUserLogic(t *testing.T) {
@@ -33,7 +35,7 @@ func TestNetworkUserLogic(t *testing.T) {
 	}
 
 	clients := []models.ExtClient{
-		models.ExtClient{
+		{
 			ClientID: "coolclient",
 		},
 	}

+ 11 - 1
logic/telemetry.go

@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/posthog/posthog-go"
@@ -84,7 +85,7 @@ func fetchTelemetryData() (telemetryData, error) {
 	data.Users = getDBLength(database.USERS_TABLE_NAME)
 	data.Networks = getDBLength(database.NETWORKS_TABLE_NAME)
 	data.Version = servercfg.GetVersion()
-	//data.Servers = GetServerCount()
+	data.Servers = getServerCount()
 	nodes, err := GetAllNodes()
 	if err == nil {
 		data.Nodes = len(nodes)
@@ -93,6 +94,15 @@ func fetchTelemetryData() (telemetryData, error) {
 	return data, err
 }
 
+// getServerCount returns number of servers from database
+func getServerCount() int {
+	data, err := database.FetchRecords(database.SERVER_UUID_TABLE_NAME)
+	if err != nil {
+		logger.Log(0, "errror retrieving server data", err.Error())
+	}
+	return len(data)
+}
+
 // setTelemetryTimestamp - Give the entry in the DB a new timestamp
 func setTelemetryTimestamp(telRecord *models.Telemetry) error {
 	lastsend := time.Now().Unix()

+ 13 - 12
models/extclient.go

@@ -2,16 +2,17 @@ package models
 
 // ExtClient - struct for external clients
 type ExtClient struct {
-	ClientID               string `json:"clientid" bson:"clientid"`
-	Description            string `json:"description" bson:"description"`
-	PrivateKey             string `json:"privatekey" bson:"privatekey"`
-	PublicKey              string `json:"publickey" bson:"publickey"`
-	Network                string `json:"network" bson:"network"`
-	Address                string `json:"address" bson:"address"`
-	Address6               string `json:"address6" bson:"address6"`
-	IngressGatewayID       string `json:"ingressgatewayid" bson:"ingressgatewayid"`
-	IngressGatewayEndpoint string `json:"ingressgatewayendpoint" bson:"ingressgatewayendpoint"`
-	LastModified           int64  `json:"lastmodified" bson:"lastmodified"`
-	Enabled                bool   `json:"enabled" bson:"enabled"`
-	OwnerID                string `json:"ownerid" bson:"ownerid"`
+	ClientID               string              `json:"clientid" bson:"clientid"`
+	Description            string              `json:"description" bson:"description"`
+	PrivateKey             string              `json:"privatekey" bson:"privatekey"`
+	PublicKey              string              `json:"publickey" bson:"publickey"`
+	Network                string              `json:"network" bson:"network"`
+	Address                string              `json:"address" bson:"address"`
+	Address6               string              `json:"address6" bson:"address6"`
+	IngressGatewayID       string              `json:"ingressgatewayid" bson:"ingressgatewayid"`
+	IngressGatewayEndpoint string              `json:"ingressgatewayendpoint" bson:"ingressgatewayendpoint"`
+	LastModified           int64               `json:"lastmodified" bson:"lastmodified"`
+	Enabled                bool                `json:"enabled" bson:"enabled"`
+	OwnerID                string              `json:"ownerid" bson:"ownerid"`
+	ACLs                   map[string]struct{} `json:"acls,omitempty" bson:"acls,omitempty"`
 }

+ 15 - 0
models/host.go

@@ -7,6 +7,21 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
+// OS_Types - list of OS types Netmaker cares about
+var OS_Types = struct {
+	Linux   string
+	Windows string
+	Mac     string
+	FreeBSD string
+	IoT     string
+}{
+	Linux:   "linux",
+	Windows: "windows",
+	Mac:     "darwin",
+	FreeBSD: "freebsd",
+	IoT:     "iot",
+}
+
 // WIREGUARD_INTERFACE name of wireguard interface
 const WIREGUARD_INTERFACE = "netmaker"
 

+ 10 - 9
models/metrics.go

@@ -15,15 +15,16 @@ type Metrics struct {
 
 // Metric - holds a metric for data between nodes
 type Metric struct {
-	NodeName      string        `json:"node_name" bson:"node_name" yaml:"node_name"`
-	Uptime        int64         `json:"uptime" bson:"uptime" yaml:"uptime"`
-	TotalTime     int64         `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
-	Latency       int64         `json:"latency" bson:"latency" yaml:"latency"`
-	TotalReceived int64         `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"`
-	TotalSent     int64         `json:"totalsent" bson:"totalsent" yaml:"totalsent"`
-	ActualUptime  time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"`
-	PercentUp     float64       `json:"percentup" bson:"percentup" yaml:"percentup"`
-	Connected     bool          `json:"connected" bson:"connected" yaml:"connected"`
+	NodeName         string        `json:"node_name" bson:"node_name" yaml:"node_name"`
+	Uptime           int64         `json:"uptime" bson:"uptime" yaml:"uptime"`
+	TotalTime        int64         `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
+	Latency          int64         `json:"latency" bson:"latency" yaml:"latency"`
+	TotalReceived    int64         `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"`
+	TotalSent        int64         `json:"totalsent" bson:"totalsent" yaml:"totalsent"`
+	ActualUptime     time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"`
+	PercentUp        float64       `json:"percentup" bson:"percentup" yaml:"percentup"`
+	Connected        bool          `json:"connected" bson:"connected" yaml:"connected"`
+	CollectedByProxy bool          `json:"collected_by_proxy" bson:"collected_by_proxy" yaml:"collected_by_proxy"`
 }
 
 // IDandAddr - struct to hold ID and primary Address

+ 2 - 3
models/migrate.go

@@ -2,7 +2,6 @@ package models
 
 // MigrationData struct needed to create new v0.18.0 node from v.0.17.X node
 type MigrationData struct {
-	JoinData     JoinData
-	LegacyNodeID string
-	Password     string
+	NewHost     Host
+	LegacyNodes []LegacyNode
 }

+ 1 - 0
models/mqtt.go

@@ -41,6 +41,7 @@ type PeerRouteInfo struct {
 	PeerAddr net.IPNet `json:"peer_addr" yaml:"peer_addr"`
 	PeerKey  string    `json:"peer_key" yaml:"peer_key"`
 	Allow    bool      `json:"allow" yaml:"allow"`
+	ID       string    `json:"id,omitempty" yaml:"id,omitempty"`
 }
 
 // ExtClientInfo - struct for ext. client and it's peers

+ 1 - 0
models/proxy.go

@@ -37,6 +37,7 @@ type RelayedConf struct {
 type PeerConf struct {
 	Proxy            bool         `json:"proxy"`
 	PublicListenPort int32        `json:"public_listen_port"`
+	ProxyListenPort  int          `json:"proxy_listen_port"`
 	IsExtClient      bool         `json:"is_ext_client"`
 	Address          net.IP       `json:"address"`
 	ExtInternalIp    net.IP       `json:"ext_internal_ip"`

+ 44 - 2
mq/handlers.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"math"
 	"time"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
@@ -185,10 +186,14 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 				logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
 				return
 			} else {
-				if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil); err != nil {
+				if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil, nil); err != nil {
 					logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
 					return
 				}
+				if err = handleNewNodeDNS(&hu.Host, &hu.Node); err != nil {
+					logger.Log(0, "failed to send dns update after node,", hu.Node.ID.String(), ", added to host", hu.Host.Name, err.Error())
+					return
+				}
 			}
 		}
 	case models.UpdateHost:
@@ -279,7 +284,7 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 			logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
 			host, err := logic.GetHost(currentNode.HostID.String())
 			if err == nil {
-				if err = PublishSingleHostPeerUpdate(context.Background(), host, nil); err != nil {
+				if err = PublishSingleHostPeerUpdate(context.Background(), host, nil, nil); err != nil {
 					logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
 				}
 			}
@@ -362,6 +367,21 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 		oldMetric := oldMetrics.Connectivity[k]
 		currMetric.TotalTime += oldMetric.TotalTime
 		currMetric.Uptime += oldMetric.Uptime // get the total uptime for this connection
+		if currMetric.CollectedByProxy {
+			currMetric.TotalReceived += oldMetric.TotalReceived
+			currMetric.TotalSent += oldMetric.TotalSent
+		} else {
+			if currMetric.TotalReceived < oldMetric.TotalReceived {
+				currMetric.TotalReceived += oldMetric.TotalReceived
+			} else {
+				currMetric.TotalReceived += int64(math.Abs(float64(currMetric.TotalReceived) - float64(oldMetric.TotalReceived)))
+			}
+			if currMetric.TotalSent < oldMetric.TotalSent {
+				currMetric.TotalSent += oldMetric.TotalSent
+			} else {
+				currMetric.TotalSent += int64(math.Abs(float64(currMetric.TotalSent) - float64(oldMetric.TotalSent)))
+			}
+		}
 		if currMetric.Uptime == 0 || currMetric.TotalTime == 0 {
 			currMetric.PercentUp = 0
 		} else {
@@ -405,3 +425,25 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 	}
 	return shouldUpdate
 }
+
+func handleNewNodeDNS(host *models.Host, node *models.Node) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSInsert,
+		Name:   host.Name + "." + node.Network,
+	}
+	if node.Address.IP != nil {
+		dns.Address = node.Address.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return err
+		}
+	} else if node.Address6.IP != nil {
+		dns.Address = node.Address6.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return err
+		}
+	}
+	if err := PublishAllDNS(node); err != nil {
+		return err
+	}
+	return nil
+}

+ 27 - 5
mq/publishers.go

@@ -27,7 +27,7 @@ func PublishPeerUpdate() error {
 	logic.ResetPeerUpdateContext()
 	for _, host := range hosts {
 		host := host
-		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, nil); err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 	}
@@ -49,7 +49,29 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
 	logic.ResetPeerUpdateContext()
 	for _, host := range hosts {
 		host := host
-		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, delNode); err != nil {
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, delNode, nil); err != nil {
+			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
+		}
+	}
+	return err
+}
+
+// PublishDeletedClientPeerUpdate --- determines and publishes a peer update
+// to all the hosts with a deleted ext client to account for
+func PublishDeletedClientPeerUpdate(delClient *models.ExtClient) error {
+	if !servercfg.IsMessageQueueBackend() {
+		return nil
+	}
+
+	hosts, err := logic.GetAllHosts()
+	if err != nil {
+		logger.Log(1, "err getting all hosts", err.Error())
+		return err
+	}
+	logic.ResetPeerUpdateContext()
+	for _, host := range hosts {
+		host := host
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, delClient); err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 	}
@@ -57,9 +79,9 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
 }
 
 // PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
-func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deletedNode *models.Node) error {
+func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deletedNode *models.Node, deletedClient *models.ExtClient) error {
 
-	peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, deletedNode)
+	peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, deletedNode, deletedClient)
 	if err != nil {
 		return err
 	}
@@ -429,7 +451,7 @@ func sendPeers() {
 		for _, host := range hosts {
 			host := host
 			logger.Log(2, "sending scheduled peer update (5 min)")
-			if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
+			if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, nil); err != nil {
 				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
 			}
 		}

+ 8 - 0
mq/util.go

@@ -12,6 +12,10 @@ import (
 )
 
 func decryptMsgWithHost(host *models.Host, msg []byte) ([]byte, error) {
+	if host.OS == models.OS_Types.IoT { // just pass along IoT messages
+		return msg, nil
+	}
+
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey() // get server private key
 	if trafficErr != nil {
 		return nil, trafficErr
@@ -41,6 +45,10 @@ func decryptMsg(node *models.Node, msg []byte) ([]byte, error) {
 }
 
 func encryptMsg(host *models.Host, msg []byte) ([]byte, error) {
+	if host.OS == models.OS_Types.IoT {
+		return msg, nil
+	}
+
 	// fetch server public key to be certain hasn't changed in transit
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey()
 	if trafficErr != nil {

+ 542 - 357
scripts/nm-quick.sh

@@ -30,100 +30,219 @@ unset INSTALL_TYPE
 unset BUILD_TYPE
 unset BUILD_TAG
 unset IMAGE_TAG
+unset AUTO_BUILD
 
-usage () {(
-    echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag]"
+# usage - displays usage instructions
+usage () {
+    echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto]"
     echo "  -e      if specified, will install netmaker EE"
     echo "  -b      type of build; options:"
 	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
 	echo "          \"local\": - will install by cloning repo and and building images from git"
-	echo "          \"branch\": - will install a specific branch using remote git and dockerhub "
+	echo "          \"branch\": - will install a specific branch using remote git and dockerhub"
     echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
+    echo "  -a      auto-build; skip prompts and use defaults, if none provided"
     echo "examples:"
 	echo "          nm-quick.sh -e -b version -t v0.18.3"
 	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"	
 	echo "          nm-quick.sh -e -b branch -t develop"
     exit 1
-)}
+}
 
-while getopts evb:t: flag
+while getopts evab:t: flag
 do
-    case "${flag}" in
-        e) 
+	case "${flag}" in
+		e) 
 			INSTALL_TYPE="ee"
 			;;
 		v) 
 			usage
 			exit 0
 			;;
-        b) 
+		a) 
+			AUTO_BUILD="on"
+			;;			
+		b) 
 			BUILD_TYPE=${OPTARG}
 			if [[ ! "$BUILD_TYPE" =~ ^(version|local|branch)$ ]]; then
-    			echo "error: $BUILD_TYPE is invalid"
+				echo "error: $BUILD_TYPE is invalid"
 				echo "valid options: version, local, branch"
 				usage
 				exit 1
 			fi
 			;;
-        t) 
+		t) 
 			BUILD_TAG=${OPTARG}
 			;;
-    esac
+	esac
 done
 
-if [ -z "$BUILD_TYPE" ]; then
-	BUILD_TYPE="version"
-	BUILD_TAG=$LATEST
-fi
+# print_logo - prints the netmaker logo
+print_logo() {
+cat << "EOF"
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+                                                                                         
+ __   __     ______     ______   __    __     ______     __  __     ______     ______    
+/\ "-.\ \   /\  ___\   /\__  _\ /\ "-./  \   /\  __ \   /\ \/ /    /\  ___\   /\  == \   
+\ \ \-.  \  \ \  __\   \/_/\ \/ \ \ \-./\ \  \ \  __ \  \ \  _"-.  \ \  __\   \ \  __<   
+ \ \_\\"\_\  \ \_____\    \ \_\  \ \_\ \ \_\  \ \_\ \_\  \ \_\ \_\  \ \_____\  \ \_\ \_\ 
+  \/_/ \/_/   \/_____/     \/_/   \/_/  \/_/   \/_/\/_/   \/_/\/_/   \/_____/   \/_/ /_/ 
+                                                                                                                                                                                                 
 
-if [ -z "$BUILD_TAG" ] && [ "$BUILD_TYPE" = "version" ]; then
-	BUILD_TAG=$LATEST
-fi
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+EOF
+}
 
-if [ -z "$BUILD_TAG" ] && [ ! -z "$BUILD_TYPE" ]; then
-	echo "error: must specify build tag when build type \"$BUILD_TYPE\" is specified"
-	usage		
-	exit 1
-fi
+# set_buildinfo - sets the information based on script input for how the installation should be run
+set_buildinfo() {
 
-IMAGE_TAG=$(sed 's/\//-/g' <<< "$BUILD_TAG")
+	if [ -z "$BUILD_TYPE" ]; then
+		BUILD_TYPE="version"
+		BUILD_TAG=$LATEST
+	fi
 
-if [ "$1" = "ce" ]; then
-	INSTALL_TYPE="ce"
-elif [ "$1" = "ee" ]; then
-	INSTALL_TYPE="ee"
-fi
+	if [ -z "$BUILD_TAG" ] && [ "$BUILD_TYPE" = "version" ]; then
+		BUILD_TAG=$LATEST
+	fi
 
-if [ -z "$INSTALL_TYPE" ]; then
-	echo "-----------------------------------------------------"
-	echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
-	echo "EE will require you to create an account at https://dashboard.license.netmaker.io"
-	echo "-----------------------------------------------------"
-	select install_option in "Community Edition" "Enterprise Edition"; do
-	case $REPLY in
-		1)
-		echo "installing Netmaker CE"
+	if [ -z "$BUILD_TAG" ] && [ ! -z "$BUILD_TYPE" ]; then
+		echo "error: must specify build tag when build type \"$BUILD_TYPE\" is specified"
+		usage		
+		exit 1
+	fi
+
+	IMAGE_TAG=$(sed 's/\//-/g' <<< "$BUILD_TAG")
+
+	if [ "$1" = "ce" ]; then
 		INSTALL_TYPE="ce"
-		break
-		;;      
-		2)
-		echo "installing Netmaker EE"
+	elif [ "$1" = "ee" ]; then
 		INSTALL_TYPE="ee"
-		break
-		;;
-		*) echo "invalid option $REPLY";;
-	esac
-	done
-fi
-echo "-----------Build Options-----------------------------"
-echo "    EE or CE: $INSTALL_TYPE";
-echo "  Build Type: $BUILD_TYPE";
-echo "   Build Tag: $BUILD_TAG";
-echo "   Image Tag: $IMAGE_TAG";
-echo "-----------------------------------------------------"
+	fi
 
-print_logo
+	if [ "$AUTO_BUILD" = "on" ] && [ -z "$INSTALL_TYPE" ]; then
+		INSTALL_TYPE="ce"
+	elif [ -z "$INSTALL_TYPE" ]; then
+		echo "-----------------------------------------------------"
+		echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
+		echo "EE will require you to create an account at https://dashboard.license.netmaker.io"
+		echo "-----------------------------------------------------"
+		select install_option in "Community Edition" "Enterprise Edition"; do
+		case $REPLY in
+			1)
+			echo "installing Netmaker CE"
+			INSTALL_TYPE="ce"
+			break
+			;;      
+			2)
+			echo "installing Netmaker EE"
+			INSTALL_TYPE="ee"
+			break
+			;;
+			*) echo "invalid option $REPLY";;
+		esac
+		done
+	fi
+	echo "-----------Build Options-----------------------------"
+	echo "    EE or CE: $INSTALL_TYPE";
+	echo "  Build Type: $BUILD_TYPE";
+	echo "   Build Tag: $BUILD_TAG";
+	echo "   Image Tag: $IMAGE_TAG";
+	echo "-----------------------------------------------------"
 
+}
+
+# install_yq - install yq if not present
+install_yq() {
+	if ! command -v yq &> /dev/null; then
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_$(dpkg --print-architecture)
+		chmod +x /usr/bin/yq
+	fi
+	set +e
+	if ! command -v yq &> /dev/null; then
+		set -e
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_amd64
+		chmod +x /usr/bin/yq
+	fi
+	set -e
+	if ! command -v yq &> /dev/null; then
+		echo "failed to install yq. Please install yq and try again."
+		echo "https://github.com/mikefarah/yq/#install"
+		exit 1
+	fi	
+}
+
+# setup_netclient - adds netclient to docker-compose
+setup_netclient() {
+
+
+	# yq ".services.netclient += {\"container_name\": \"netclient\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"image\": \"gravitl/netclient:$IMAGE_TAG\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"hostname\": \"netmaker-1\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"network_mode\": \"host\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient.depends_on += [\"netmaker\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"restart\": \"always\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient.environment += {\"TOKEN\": \"$TOKEN\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient.volumes += [\"/etc/netclient:/etc/netclient\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient.cap_add += [\"NET_ADMIN\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient.cap_add += [\"NET_RAW\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient.cap_add += [\"SYS_MODULE\"]" -i /root/docker-compose.yml
+
+	# docker-compose up -d
+
+	set +e
+	netclient uninstall
+	set -e
+
+	wget -O netclient https://github.com/gravitl/netclient/releases/download/$LATEST/netclient_linux_amd64
+	chmod +x netclient
+	./netclient install
+	netclient join -t $TOKEN
+
+	echo "waiting for client to become available"
+	wait_seconds 10 
+}
+
+# configure_netclient - configures server's netclient as a default host and an ingress gateway
+configure_netclient() {
+
+	NODE_ID=$(sudo cat /etc/netclient/nodes.yml | yq -r .netmaker.commonnode.id)
+	echo "join complete. New node ID: $NODE_ID"
+	HOST_ID=$(sudo cat /etc/netclient/netclient.yml | yq -r .host.id)
+	echo "For first join, making host a default"
+	echo "Host ID: $HOST_ID"
+	# set as a default host
+	set +e
+	nmctl host update $HOST_ID --default
+	sleep 5
+	nmctl node create_ingress netmaker $NODE_ID
+	set -e
+}
+
+# setup_nmctl - pulls nmctl and makes it executable
+setup_nmctl() {
+
+	# DEV_TEMP - Temporary instructions for testing
+	wget -O /usr/bin/nmctl https://fileserver.netmaker.org/testing/nmctl
+
+	# RELEASE_REPLACE - Use this once release is ready
+	# wget https://github.com/gravitl/netmaker/releases/download/v0.17.1/nmctl
+    chmod +x /usr/bin/nmctl
+    echo "using server api.$NETMAKER_BASE_DOMAIN"
+    echo "using master key $MASTER_KEY"
+    nmctl context set default --endpoint="https://api.$NETMAKER_BASE_DOMAIN" --master_key="$MASTER_KEY"
+    nmctl context use default
+    RESP=$(nmctl network list)
+    if [[ $RESP == *"unauthorized"* ]]; then
+        echo "Unable to properly configure NMCTL, exiting..."
+        exit 1
+    fi
+}
+
+# wait_seconds - wait for the specified period of time
 wait_seconds() {(
   for ((a=1; a <= $1; a++))
   do
@@ -132,7 +251,11 @@ wait_seconds() {(
   done
 )}
 
+# confirm - get user input to confirm that they want to perform the next step
 confirm() {(
+  if [ "$AUTO_BUILD" = "on" ]; then
+	return 0
+  fi
   while true; do
       read -p 'Does everything look right? [y/n]: ' yn
       case $yn in
@@ -143,6 +266,7 @@ confirm() {(
   done
 )}
 
+# local_install_setup - builds artifacts based on specified branch locally to use in install
 local_install_setup() {(
 	rm -rf netmaker-tmp
 	mkdir netmaker-tmp
@@ -165,92 +289,82 @@ local_install_setup() {(
 	rm -rf netmaker-tmp
 )}
 
-echo "checking dependencies..."
-
-OS=$(uname)
-
-if [ -f /etc/debian_version ]; then
-	dependencies="git wireguard wireguard-tools jq docker.io docker-compose"
-	update_cmd='apt update'
-	install_cmd='apt-get install -y'
-elif [ -f /etc/alpine-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='apk update'
-	install_cmd='apk --update add'
-elif [ -f /etc/centos-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='yum update'
-	install_cmd='yum install -y'
-elif [ -f /etc/fedora-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='dnf update'
-	install_cmd='dnf install -y'
-elif [ -f /etc/redhat-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='yum update'
-	install_cmd='yum install -y'
-elif [ -f /etc/arch-release ]; then
-    	dependecies="git wireguard-tools jq docker.io docker-compose"
-	update_cmd='pacman -Sy'
-	install_cmd='pacman -S --noconfirm'
-elif [ "${OS}" = "FreeBSD" ]; then
-	dependencies="git wireguard wget jq docker.io docker-compose"
-	update_cmd='pkg update'
-	install_cmd='pkg install -y'
-elif [ -f /etc/turris-version ]; then
-	dependencies="git wireguard-tools bash jq docker.io docker-compose"
-	OS="TurrisOS"
-	update_cmd='opkg update'	
-	install_cmd='opkg install'
-elif [ -f /etc/openwrt_release ]; then
-	dependencies="git wireguard-tools bash jq docker.io docker-compose"
-	OS="OpenWRT"
-	update_cmd='opkg update'	
-	install_cmd='opkg install'
-else
-	install_cmd=''
-fi
+# install_dependencies - install necessary packages to run netmaker 
+install_dependencies() {
+	echo "checking dependencies..."
+
+	OS=$(uname)
+	if [ -f /etc/debian_version ]; then
+		dependencies="git wireguard wireguard-tools jq docker.io docker-compose"
+		update_cmd='apt update'
+		install_cmd='apt-get install -y'
+	elif [ -f /etc/alpine-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='apk update'
+		install_cmd='apk --update add'
+	elif [ -f /etc/centos-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='yum update'
+		install_cmd='yum install -y'
+	elif [ -f /etc/fedora-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='dnf update'
+		install_cmd='dnf install -y'
+	elif [ -f /etc/redhat-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='yum update'
+		install_cmd='yum install -y'
+	elif [ -f /etc/arch-release ]; then
+			dependecies="git wireguard-tools jq docker.io docker-compose"
+		update_cmd='pacman -Sy'
+		install_cmd='pacman -S --noconfirm'
+	elif [ "${OS}" = "FreeBSD" ]; then
+		dependencies="git wireguard wget jq docker.io docker-compose"
+		update_cmd='pkg update'
+		install_cmd='pkg install -y'
+	elif [ -f /etc/turris-version ]; then
+		dependencies="git wireguard-tools bash jq docker.io docker-compose"
+		OS="TurrisOS"
+		update_cmd='opkg update'	
+		install_cmd='opkg install'
+	elif [ -f /etc/openwrt_release ]; then
+		dependencies="git wireguard-tools bash jq docker.io docker-compose"
+		OS="OpenWRT"
+		update_cmd='opkg update'	
+		install_cmd='opkg install'
+	else
+		install_cmd=''
+	fi
 
-if [ -z "${install_cmd}" ]; then
-        echo "OS unsupported for automatic dependency install"
-	exit 1
-fi
+	if [ -z "${install_cmd}" ]; then
+			echo "OS unsupported for automatic dependency install"
+		exit 1
+	fi
 
-set -- $dependencies
+	set -- $dependencies
 
-${update_cmd}
+	${update_cmd}
 
-while [ -n "$1" ]; do
-	if [ "${OS}" = "FreeBSD" ]; then
-		is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
-		if [ "$is_installed" != "" ]; then
-			echo "  " $1 is installed
-		else
-			echo "  " $1 is not installed. Attempting install.
-			${install_cmd} $1
-			sleep 5
+	while [ -n "$1" ]; do
+		if [ "${OS}" = "FreeBSD" ]; then
 			is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
 			if [ "$is_installed" != "" ]; then
 				echo "  " $1 is installed
-			elif [ -x "$(command -v $1)" ]; then
-				echo "  " $1 is installed
 			else
-				echo "  " FAILED TO INSTALL $1
-				echo "  " This may break functionality.
-			fi
-		fi	
-	else
-		if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
-			is_installed=$(opkg list-installed $1 | grep $1)
+				echo "  " $1 is not installed. Attempting install.
+				${install_cmd} $1
+				sleep 5
+				is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
+				if [ "$is_installed" != "" ]; then
+					echo "  " $1 is installed
+				elif [ -x "$(command -v $1)" ]; then
+					echo "  " $1 is installed
+				else
+					echo "  " FAILED TO INSTALL $1
+					echo "  " This may break functionality.
+				fi
+			fi	
 		else
-			is_installed=$(dpkg-query -W --showformat='${Status}\n' $1 | grep "install ok installed")
-		fi
-		if [ "${is_installed}" != "" ]; then
-			echo "    " $1 is installed
-		else
-			echo "    " $1 is not installed. Attempting install.
-			${install_cmd} $1
-			sleep 5
 			if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
 				is_installed=$(opkg list-installed $1 | grep $1)
 			else
@@ -258,290 +372,361 @@ while [ -n "$1" ]; do
 			fi
 			if [ "${is_installed}" != "" ]; then
 				echo "    " $1 is installed
-			elif [ -x "$(command -v $1)" ]; then
-				echo "  " $1 is installed
 			else
-				echo "  " FAILED TO INSTALL $1
-				echo "  " This may break functionality.
+				echo "    " $1 is not installed. Attempting install.
+				${install_cmd} $1
+				sleep 5
+				if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
+					is_installed=$(opkg list-installed $1 | grep $1)
+				else
+					is_installed=$(dpkg-query -W --showformat='${Status}\n' $1 | grep "install ok installed")
+				fi
+				if [ "${is_installed}" != "" ]; then
+					echo "    " $1 is installed
+				elif [ -x "$(command -v $1)" ]; then
+					echo "  " $1 is installed
+				else
+					echo "  " FAILED TO INSTALL $1
+					echo "  " This may break functionality.
+				fi
 			fi
 		fi
+		shift
+	done
+
+	echo "-----------------------------------------------------"
+	echo "dependency check complete"
+	echo "-----------------------------------------------------"
+} 
+set -e
+
+# set_install_vars - sets the variables that will be used throughout installation
+set_install_vars() {
+
+	IP_ADDR=$(dig -4 myip.opendns.com @resolver1.opendns.com +short)
+	if [ "$IP_ADDR" = "" ]; then
+		IP_ADDR=$(curl -s ifconfig.me)
 	fi
-	shift
-done
 
-echo "-----------------------------------------------------"
-echo "dependency check complete"
-echo "-----------------------------------------------------"
+	NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
+	COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
+	SERVER_PUBLIC_IP=$IP_ADDR
+	MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+	DOMAIN_TYPE=""
+	echo "-----------------------------------------------------"
+	echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
+	echo "To use your own domain, add a Wildcard DNS record (e.x: *.netmaker.example.com) pointing to $SERVER_PUBLIC_IP"
+	echo "-----------------------------------------------------"
 
-wait_seconds 3
+	if [ "$AUTO_BUILD" = "on" ]; then
+			DOMAIN_TYPE="auto"
+	else
+		select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain (e.x: netmaker.example.com)"; do
+		case $REPLY in
+			1)
+			echo "using $NETMAKER_BASE_DOMAIN for base domain"
+			DOMAIN_TYPE="auto"
+			break
+			;;      
+			2)
+			read -p "Enter Custom Domain (make sure  *.domain points to $SERVER_PUBLIC_IP first): " domain
+			NETMAKER_BASE_DOMAIN=$domain
+			echo "using $NETMAKER_BASE_DOMAIN"
+			DOMAIN_TYPE="custom"
+			break
+			;;
+			*) echo "invalid option $REPLY";;
+		esac
+		done
+	fi
 
+	wait_seconds 2
 
-if [ "$BUILD_TYPE" = "local" ]; then
-	local_install_setup
-fi
+	echo "-----------------------------------------------------"
+	echo "The following subdomains will be used:"
+	echo "          dashboard.$NETMAKER_BASE_DOMAIN"
+	echo "                api.$NETMAKER_BASE_DOMAIN"
+	echo "             broker.$NETMAKER_BASE_DOMAIN"
 
-set -e
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		echo "         prometheus.$NETMAKER_BASE_DOMAIN"
+		echo "  netmaker-exporter.$NETMAKER_BASE_DOMAIN"
+		echo "            grafana.$NETMAKER_BASE_DOMAIN"
+	fi
 
-IP_ADDR=$(dig -4 myip.opendns.com @resolver1.opendns.com +short)
-if [ "$IP_ADDR" = "" ]; then
-	IP_ADDR=$(curl -s ifconfig.me)
-fi
+	echo "-----------------------------------------------------"
 
-NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
-COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
-SERVER_PUBLIC_IP=$IP_ADDR
-MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
-DOMAIN_TYPE=""
-echo "-----------------------------------------------------"
-echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
-echo "To use your own domain, add a Wildcard DNS record (e.x: *.netmaker.example.com) pointing to $SERVER_PUBLIC_IP"
-echo "-----------------------------------------------------"
-select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain (e.x: netmaker.example.com)"; do
-  case $REPLY in
-    1)
-      echo "using $NETMAKER_BASE_DOMAIN for base domain"
-      DOMAIN_TYPE="auto"
-	  break
-      ;;      
-    2)
-      read -p "Enter Custom Domain (make sure  *.domain points to $SERVER_PUBLIC_IP first): " domain
-      NETMAKER_BASE_DOMAIN=$domain
-      echo "using $NETMAKER_BASE_DOMAIN"
-      DOMAIN_TYPE="custom"
-      break
-      ;;
-    *) echo "invalid option $REPLY";;
-  esac
-done
+	if [[ "$DOMAIN_TYPE" == "custom" ]]; then
+		echo "before continuing, confirm DNS is configured correctly, with records pointing to $SERVER_PUBLIC_IP"
+		confirm
+	fi
 
-wait_seconds 2
+	wait_seconds 1
 
-echo "-----------------------------------------------------"
-echo "The following subdomains will be used:"
-echo "          dashboard.$NETMAKER_BASE_DOMAIN"
-echo "                api.$NETMAKER_BASE_DOMAIN"
-echo "             broker.$NETMAKER_BASE_DOMAIN"
+	if [ "$INSTALL_TYPE" = "ee" ]; then
 
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	echo "         prometheus.$NETMAKER_BASE_DOMAIN"
-	echo "  netmaker-exporter.$NETMAKER_BASE_DOMAIN"
-	echo "            grafana.$NETMAKER_BASE_DOMAIN"
-fi
+		echo "-----------------------------------------------------"
+		echo "Provide Details for EE installation:"
+		echo "    1. Log into https://dashboard.license.netmaker.io"
+		echo "    2. Copy License Key Value: https://dashboard.license.netmaker.io/license-keys"
+		echo "    3. Retrieve Account ID: https://dashboard.license.netmaker.io/user"
+		echo "    4. note email address"
+		echo "-----------------------------------------------------"
+		unset LICENSE_KEY
+		while [ -z "$LICENSE_KEY" ]; do
+			read -p "License Key: " LICENSE_KEY
+		done
+		unset ACCOUNT_ID
+		while [ -z ${ACCOUNT_ID} ]; do
+			read -p "Account ID: " ACCOUNT_ID
+		done
+	fi
+
+	unset GET_EMAIL
+	unset RAND_EMAIL
+	RAND_EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
+	if [ -z $AUTO_BUILD ]; then
+		read -p "Email Address for Domain Registration (click 'enter' to use $RAND_EMAIL): " GET_EMAIL
+	fi
+	if [ -z "$GET_EMAIL" ]; then
+	echo "using rand email"
+	EMAIL="$RAND_EMAIL"
+	else
+	EMAIL="$GET_EMAIL"
+	fi
+
+	wait_seconds 1
+
+	unset GET_MQ_USERNAME
+	unset GET_MQ_PASSWORD
+	unset CONFIRM_MQ_PASSWORD
+	echo "Enter Credentials For MQ..."
+	if [ -z $AUTO_BUILD ]; then
+		read -p "MQ Username (click 'enter' to use 'netmaker'): " GET_MQ_USERNAME
+	fi
+	if [ -z "$GET_MQ_USERNAME" ]; then
+	echo "using default username for mq"
+	MQ_USERNAME="netmaker"
+	else
+	MQ_USERNAME="$GET_MQ_USERNAME"
+	fi
+
+	MQ_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+
+	if [ -z $AUTO_BUILD ]; then  
+		select domain_option in "Auto Generated Password" "Input Your Own Password"; do
+			case $REPLY in
+			1)
+			echo "using random password for mq"
+			break
+			;;      
+			2)
+			while true
+			do
+				echo "Enter your Password For MQ: " 
+				read -s GET_MQ_PASSWORD
+				echo "Enter your password again to confirm: "
+				read -s CONFIRM_MQ_PASSWORD
+				if [ ${GET_MQ_PASSWORD} != ${CONFIRM_MQ_PASSWORD} ]; then
+					echo "wrong password entered, try again..."
+					continue
+				fi
+				MQ_PASSWORD="$GET_MQ_PASSWORD"
+				echo "MQ Password Saved Successfully!!"
+				break
+			done
+			break
+			;;
+			*) echo "invalid option $REPLY";;
+		esac
+		done
+	fi
 
-echo "-----------------------------------------------------"
+	wait_seconds 2
+
+	echo "-----------------------------------------------------------------"
+	echo "                SETUP ARGUMENTS"
+	echo "-----------------------------------------------------------------"
+	echo "        domain: $NETMAKER_BASE_DOMAIN"
+	echo "         email: $EMAIL"
+	echo "     public ip: $SERVER_PUBLIC_IP"
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		echo "       license: $LICENSE_KEY"
+		echo "    account id: $ACCOUNT_ID"
+	fi
+	echo "-----------------------------------------------------------------"
+	echo "Confirm Settings for Installation"
+	echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
 
-if [[ "$DOMAIN_TYPE" == "custom" ]]; then
-	echo "before continuing, confirm DNS is configured correctly, with records pointing to $SERVER_PUBLIC_IP"
 	confirm
-fi
 
-wait_seconds 1
+}
 
-if [ "$INSTALL_TYPE" = "ee" ]; then
+# install_netmaker - sets the config files and starts docker-compose
+install_netmaker() {
 
-	echo "-----------------------------------------------------"
-	echo "Provide Details for EE installation:"
-	echo "    1. Log into https://dashboard.license.netmaker.io"
-	echo "    2. Copy License Key Value: https://dashboard.license.netmaker.io/license-keys"
-	echo "    3. Retrieve Account ID: https://dashboard.license.netmaker.io/user"
-	echo "    4. note email address"
-	echo "-----------------------------------------------------"
-	unset LICENSE_KEY
-	while [ -z "$LICENSE_KEY" ]; do
-		read -p "License Key: " LICENSE_KEY
-	done
-	unset ACCOUNT_ID
-	while [ -z ${ACCOUNT_ID} ]; do
-		read -p "Account ID: " ACCOUNT_ID
-	done
+	echo "-----------------------------------------------------------------"
+	echo "Beginning installation..."
+	echo "-----------------------------------------------------------------"
 
-fi
+	wait_seconds 3
 
-unset GET_EMAIL
-unset RAND_EMAIL
-RAND_EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
-read -p "Email Address for Domain Registration (click 'enter' to use $RAND_EMAIL): " GET_EMAIL
-if [ -z "$GET_EMAIL" ]; then
-  echo "using rand email"
-  EMAIL="$RAND_EMAIL"
-else
-  EMAIL="$GET_EMAIL"
-fi
+	echo "Pulling config files..."
 
-wait_seconds 1
-
-unset GET_MQ_USERNAME
-unset GET_MQ_PASSWORD
-unset CONFIRM_MQ_PASSWORD
-echo "Enter Credentials For MQ..."
-read -p "MQ Username (click 'enter' to use 'netmaker'): " GET_MQ_USERNAME
-if [ -z "$GET_MQ_USERNAME" ]; then
-  echo "using default username for mq"
-  MQ_USERNAME="netmaker"
-else
-  MQ_USERNAME="$GET_MQ_USERNAME"
-fi
 
-select domain_option in "Auto Generated Password" "Input Your Own Password"; do
-	case $REPLY in
-	1)
-	echo "generating random password for mq"
-	MQ_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
-	break
-	;;      
-    2)
-	while true
-    do
-        echo "Enter your Password For MQ: " 
-        read -s GET_MQ_PASSWORD
-        echo "Enter your password again to confirm: "
-        read -s CONFIRM_MQ_PASSWORD
-        if [ ${GET_MQ_PASSWORD} != ${CONFIRM_MQ_PASSWORD} ]; then
-            echo "wrong password entered, try again..."
-            continue
-        fi
-		MQ_PASSWORD="$GET_MQ_PASSWORD"
-        echo "MQ Password Saved Successfully!!"
-        break
-    done
-      break
-      ;;
-    *) echo "invalid option $REPLY";;
-  esac
-done
+	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.yml" 
+	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile"
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.ee.yml" 
+		CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile-EE"
+	fi
+	if [ ! "$BUILD_TYPE" = "local" ]; then
+		wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
+		wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/wait.sh
+	fi
 
+	chmod +x /root/wait.sh
+	mkdir -p /etc/netmaker
 
-wait_seconds 2
+	echo "Setting docker-compose and Caddyfile..."
 
-echo "-----------------------------------------------------------------"
-echo "                SETUP ARGUMENTS"
-echo "-----------------------------------------------------------------"
-echo "        domain: $NETMAKER_BASE_DOMAIN"
-echo "         email: $EMAIL"
-echo "     public ip: $SERVER_PUBLIC_IP"
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	echo "       license: $LICENSE_KEY"
-	echo "    account id: $ACCOUNT_ID"
-fi
-echo "-----------------------------------------------------------------"
-echo "Confirm Settings for Installation"
-echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
+	sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
+	sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile
+	sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
+	sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
+	sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
+	sed -i "s/REPLACE_MQ_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml
+	sed -i "s/REPLACE_MQ_USERNAME/$MQ_USERNAME/g" /root/docker-compose.yml 
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
+		sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
+	fi
 
-confirm
+	if [ "$BUILD_TYPE" = "version" ] && [ "$INSTALL_TYPE" = "ee" ]; then
+		sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG-ee/g" /root/docker-compose.yml
+	else
+		sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+	fi
 
+	if [ "$BUILD_TYPE" = "local" ]; then
+		sed -i "s/REPLACE_UI_IMAGE_TAG/$LATEST/g" /root/docker-compose.yml
+	else
+		sed -i "s/REPLACE_UI_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+	fi
 
-echo "-----------------------------------------------------------------"
-echo "Beginning installation..."
-echo "-----------------------------------------------------------------"
+	echo "Starting containers..."
 
-wait_seconds 3
+	docker-compose -f /root/docker-compose.yml up -d
 
-echo "Pulling config files..."
+	wait_seconds 2
 
+}
 
-COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.yml" 
-CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile"
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.ee.yml" 
-	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile-EE"
-fi
-if [ ! "$BUILD_TYPE" = "local" ]; then
-	wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
-	wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/wait.sh
-fi
+# test_connection - tests to make sure Caddy has proper SSL certs
+test_connection() {
 
-chmod +x /root/wait.sh
-mkdir -p /etc/netmaker
-
-echo "Setting docker-compose and Caddyfile..."
-
-sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
-sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile
-sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
-sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
-sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
-sed -i "s/REPLACE_MQ_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml
-sed -i "s/REPLACE_MQ_USERNAME/$MQ_USERNAME/g" /root/docker-compose.yml 
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
-	sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
-fi
+	echo "Testing Caddy setup (please be patient, this may take 1-2 minutes)"
+	for i in 1 2 3 4 5 6 7 8
+	do
+	curlresponse=$(curl -vIs https://api.${NETMAKER_BASE_DOMAIN} 2>&1)
 
-if [ "$BUILD_TYPE" = "version" ] && [ "$INSTALL_TYPE" = "ee" ]; then
-	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG-ee/g" /root/docker-compose.yml
-else
-	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
-fi
+	if [[ "$i" == 8 ]]; then
+	echo "    Caddy is having an issue setting up certificates, please investigate (docker logs caddy)"
+	echo "    Exiting..."
+	exit 1
+	elif [[ "$curlresponse" == *"failed to verify the legitimacy of the server"* ]]; then
+	echo "    Certificates not yet configured, retrying..."
 
-if [ "$BUILD_TYPE" = "local" ]; then
-	sed -i "s/REPLACE_UI_IMAGE_TAG/$LATEST/g" /root/docker-compose.yml
-else
-	sed -i "s/REPLACE_UI_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
-fi
+	elif [[ "$curlresponse" == *"left intact"* ]]; then
+	echo "    Certificates ok"
+	break
+	else
+	secs=$(($i*5+10))
+	echo "    Issue establishing connection...retrying in $secs seconds..."       
+	fi
+	sleep $secs
+	done
 
-echo "Starting containers..."
+}
 
-docker-compose -f /root/docker-compose.yml up -d
+# setup_mesh - sets up a default mesh network on the server
+setup_mesh() {
 
-sleep 2
+	wait_seconds 5
 
-test_connection() {
+	echo "Creating netmaker network (10.101.0.0/16)"
+
+	nmctl network create --name netmaker --ipv4_addr 10.101.0.0/16
+
+	wait_seconds 5
+
+	echo "Creating netmaker access key"
+
+	nmctl keys create test1 99999 --name netmaker-key
+	tokenJson=$(nmctl keys create netmaker 2)
+	TOKEN=$(jq -r '.accessstring' <<< ${tokenJson})
+
+	wait_seconds 3
 
-echo "Testing Caddy setup (please be patient, this may take 1-2 minutes)"
-for i in 1 2 3 4 5 6 7 8
-do
-curlresponse=$(curl -vIs https://api.${NETMAKER_BASE_DOMAIN} 2>&1)
-
-if [[ "$i" == 8 ]]; then
-  echo "    Caddy is having an issue setting up certificates, please investigate (docker logs caddy)"
-  echo "    Exiting..."
-  exit 1
-elif [[ "$curlresponse" == *"failed to verify the legitimacy of the server"* ]]; then
-  echo "    Certificates not yet configured, retrying..."
-
-elif [[ "$curlresponse" == *"left intact"* ]]; then
-  echo "    Certificates ok"
-  break
-else
-  secs=$(($i*5+10))
-  echo "    Issue establishing connection...retrying in $secs seconds..."       
-fi
-sleep $secs
-done
 }
 
+# print_success - prints a success message upon completion
+print_success() {
+	echo "-----------------------------------------------------------------"
+	echo "-----------------------------------------------------------------"
+	echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
+	echo "Visit dashboard.$NETMAKER_BASE_DOMAIN to log in"
+	echo "-----------------------------------------------------------------"
+	echo "-----------------------------------------------------------------"
+}
 
-setup_mesh() {( set -e
+# 1. print netmaker logo
+print_logo
 
-wait_seconds 15
+# 2. setup the build instructions
+set_buildinfo
 
-echo "Creating netmaker network (10.101.0.0/16)"
+set +e
 
-curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"netmaker"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
+# 3. install necessary packages
+install_dependencies
 
-wait_seconds 5
+# 4. install yq if necessary
+install_yq
 
-echo "Creating netmaker access key"
+# 5. if running a local build, clone git and build artifacts
+if [ "$BUILD_TYPE" = "local" ]; then
+	local_install_setup
+fi
 
-curlresponse=$(curl -s -d '{"uses":99999,"name":"netmaker-key"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks/netmaker/keys)
-ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
+set -e
 
-wait_seconds 3
+# 6. get user input for variables
+set_install_vars
 
-)}
+# 7. get and set config files, startup docker-compose
+install_netmaker
 
 set +e
+
+# 8. make sure Caddy certs are working
 test_connection
 
-wait_seconds 3
+# 9. install the netmaker CLI
+setup_nmctl
 
+# 10. create a default mesh network for netmaker
 setup_mesh
 
-echo "-----------------------------------------------------------------"
-echo "-----------------------------------------------------------------"
-echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
-echo "Visit dashboard.$NETMAKER_BASE_DOMAIN to log in"
-echo "-----------------------------------------------------------------"
-echo "-----------------------------------------------------------------"
+set -e
+
+# 11. add netclient to docker-compose and start it up
+setup_netclient
+
+# 12. make the netclient a default host and ingress gw
+configure_netclient
+
+# 13. print success message
+print_success
 
 # cp -f /etc/skel/.bashrc /root/.bashrc

+ 27 - 21
scripts/nm-upgrade.sh

@@ -39,13 +39,8 @@ confirm() {
 # install_dependencies - install system dependencies necessary for script to run
 install_dependencies() {
   OS=$(uname)
-  is_ubuntu=$(sudo cat /etc/lsb-release | grep "Ubuntu")
-  if [ "${is_ubuntu}" != "" ]; then
-    dependencies="yq jq wireguard jq docker.io docker-compose"
-    update_cmd='apt update'
-    install_cmd='snap install'
-  elif [ -f /etc/debian_version ]; then
-    dependencies="yq jq wireguard jq docker.io docker-compose"
+  if [ -f /etc/debian_version ]; then
+    dependencies="jq wireguard jq docker.io docker-compose"
     update_cmd='apt update'
     install_cmd='apt install -y'
   elif [ -f /etc/centos-release ]; then
@@ -105,20 +100,24 @@ install_dependencies() {
   echo "-----------------------------------------------------"
 }
 
-# get_email- gets upgrader's email address 
-get_email() {
-
-  unset GET_EMAIL
-  unset RAND_EMAIL
-  RAND_EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
-  read -p "Email Address for Domain Registration (click 'enter' to use $RAND_EMAIL): " GET_EMAIL
-  if [ -z "$GET_EMAIL" ]; then
-    echo "using rand email"
-    EMAIL="$RAND_EMAIL"
-  else
-    EMAIL="$GET_EMAIL"
-  fi
-
+# install_yq - install yq if not present
+install_yq() {
+	if ! command -v yq &> /dev/null; then
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_$(dpkg --print-architecture)
+		chmod +x /usr/bin/yq
+	fi
+	set +e
+	if ! command -v yq &> /dev/null; then
+		set -e
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_amd64
+		chmod +x /usr/bin/yq
+	fi
+	set -e
+	if ! command -v yq &> /dev/null; then
+		echo "failed to install yq. Please install yq and try again."
+		echo "https://github.com/mikefarah/yq/#install"
+		exit 1
+	fi	
 }
 
 # collect_server_settings - retrieve server settings from existing compose file
@@ -602,9 +601,16 @@ if [ $(id -u) -ne 0 ]; then
    exit 1
 fi
 
+set +e
+
 echo "...installing dependencies for script"
 install_dependencies
 
+echo "...installing yq if necessary"
+install_yq
+
+set -e
+
 echo "...confirming version is correct"
 check_version