Browse Source

Merge pull request #2521 from gravitl/release-v0.20.6

v0.20.6
Abhishek K 2 years ago
parent
commit
c884feaf9a
58 changed files with 1492 additions and 772 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 1 0
      .github/workflows/test.yml
  3. 1 1
      Dockerfile
  4. 1 1
      Dockerfile-quick
  5. 1 1
      README.md
  6. 14 5
      compose/docker-compose-emqx.yml
  7. 1 1
      compose/docker-compose.netclient.yml
  8. 4 1
      compose/docker-compose.yml
  9. 4 2
      config/config.go
  10. 7 0
      controllers/controller.go
  11. 1 1
      controllers/docs.go
  12. 76 61
      controllers/ext_client.go
  13. 45 3
      controllers/hosts.go
  14. 40 16
      controllers/limits.go
  15. 169 43
      controllers/migrate.go
  16. 9 1
      controllers/network.go
  17. 2 2
      controllers/network_test.go
  18. 49 27
      controllers/node.go
  19. 2 1
      controllers/regex.go
  20. 17 3
      controllers/regex_test.go
  21. 27 16
      controllers/server.go
  22. 1 1
      controllers/user.go
  23. 17 0
      ee/ee_controllers/middleware.go
  24. 11 4
      ee/initialize.go
  25. 46 23
      ee/license.go
  26. 77 0
      ee/license_test.go
  27. 39 22
      ee/types.go
  28. 14 5
      ee/util.go
  29. 6 6
      go.mod
  30. 12 12
      go.sum
  31. 1 1
      k8s/client/netclient-daemonset.yaml
  32. 1 1
      k8s/client/netclient.yaml
  33. 1 1
      k8s/server/netmaker-ui.yaml
  34. 15 0
      logic/clients.go
  35. 5 9
      logic/extpeers.go
  36. 36 12
      logic/gateway.go
  37. 7 8
      logic/hosts.go
  38. 31 0
      logic/nodes.go
  39. 19 16
      logic/serverconf.go
  40. 8 4
      logic/timer.go
  41. 2 2
      logic/zombie.go
  42. 2 1
      main.go
  43. 0 6
      models/api_host.go
  44. 2 1
      models/host.go
  45. 3 1
      models/migrate.go
  46. 4 4
      models/network.go
  47. 4 11
      models/node.go
  48. 2 2
      mq/publishers.go
  49. 7 10
      release.md
  50. 0 78
      scripts/netclient-install.ps1
  51. 0 307
      scripts/netclient-install.sh
  52. 1 1
      scripts/netmaker.default.env
  53. 26 12
      scripts/nm-certs.sh
  54. 12 5
      scripts/nm-quick.sh
  55. 1 1
      scripts/nm-upgrade-0-17-1-to-0-19-0.sh
  56. 574 0
      scripts/nm-upgrade.sh
  57. 32 18
      servercfg/serverconf.go
  58. 1 1
      swagger.yaml

+ 1 - 0
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -31,6 +31,7 @@ body:
       label: Version
       label: Version
       description: What version are you running?
       description: What version are you running?
       options:
       options:
+        - v0.20.6
         - v0.20.5
         - v0.20.5
         - v0.20.4
         - v0.20.4
         - v0.20.3
         - v0.20.3

+ 1 - 0
.github/workflows/test.yml

@@ -51,6 +51,7 @@ jobs:
         run: |
         run: |
           go vet ./...
           go vet ./...
           go test -p 1 ./... -v
           go test -p 1 ./... -v
+          go test -p 1 ./ee -v --tags ee
         env:
         env:
           DATABASE: sqlite
           DATABASE: sqlite
           CLIENT_MODE: "off"
           CLIENT_MODE: "off"

+ 1 - 1
Dockerfile

@@ -6,7 +6,7 @@ COPY . .
 
 
 RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
 # RUN go build -tags=ee . -o netmaker main.go
-FROM alpine:3.18.2
+FROM alpine:3.18.3
 
 
 # add a c lib
 # add a c lib
 # set the working directory
 # set the working directory

+ 1 - 1
Dockerfile-quick

@@ -1,5 +1,5 @@
 #first stage - builder
 #first stage - builder
-FROM alpine:3.18.2
+FROM alpine:3.18.3
 ARG version 
 ARG version 
 WORKDIR /app
 WORKDIR /app
 COPY ./netmaker /root/netmaker
 COPY ./netmaker /root/netmaker

+ 1 - 1
README.md

@@ -16,7 +16,7 @@
 
 
 <p align="center">
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.20.5-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.20.6-informational?style=flat-square" />
   </a>
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

+ 14 - 5
compose/docker-compose-emqx.yml

@@ -4,14 +4,23 @@ services:
   mq:
   mq:
     container_name: mq
     container_name: mq
     image: emqx/emqx:5.0.9
     image: emqx/emqx:5.0.9
+    env_file: ./netmaker.env
     restart: unless-stopped
     restart: unless-stopped
     environment:
     environment:
-      - EMQX_NAME: "emqx"
+      - EMQX_NAME=emqx
       - EMQX_DASHBOARD__DEFAULT_PASSWORD=${MQ_PASSWORD}
       - EMQX_DASHBOARD__DEFAULT_PASSWORD=${MQ_PASSWORD}
       - EMQX_DASHBOARD__DEFAULT_USERNAME=${MQ_USERNAME}
       - EMQX_DASHBOARD__DEFAULT_USERNAME=${MQ_USERNAME}
     ports:
     ports:
-      - "1883:1883" # MQTT
-      - "8883:8883" # SSL MQTT
-      - "8083:8083" # Websockets
+      - "1883:1883"   # MQTT
+      - "8883:8883"   # SSL MQTT
+      - "8083:8083"   # Websockets
+      - "8084:8084"   # SSL Websockets
       - "18083:18083" # Dashboard/REST_API
       - "18083:18083" # Dashboard/REST_API
-
+    volumes:
+      - emqx_data:/opt/emqx/data
+      - emqx_etc:/opt/emqx/etc
+      - emqx_logs:/opt/emqx/log
+volumes:
+  emqx_data: { } # storage for emqx data
+  emqx_etc: { }  # storage for emqx etc
+  emqx_logs: { } # storage for emqx logs

+ 1 - 1
compose/docker-compose.netclient.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
 services:
   netclient:
   netclient:
     container_name: netclient
     container_name: netclient
-    image: 'gravitl/netclient:v0.20.5'
+    image: 'gravitl/netclient:v0.20.6'
     hostname: netmaker-1
     hostname: netmaker-1
     network_mode: host
     network_mode: host
     restart: on-failure
     restart: on-failure

+ 4 - 1
compose/docker-compose.yml

@@ -14,7 +14,10 @@ services:
       # config-dependant vars
       # config-dependant vars
       - STUN_LIST=stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
       - STUN_LIST=stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
       # The domain/host IP indicating the mq broker address
       # The domain/host IP indicating the mq broker address
-      - BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}
+      - BROKER_ENDPOINT=wss://broker.${NM_DOMAIN} # For EMQX broker use `BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}/mqtt`
+      # For EMQX broker (uncomment the two lines below)
+      #- BROKER_TYPE=emqx
+      #- EMQX_REST_ENDPOINT=http://mq:18083
       # The base domain of netmaker
       # The base domain of netmaker
       - SERVER_NAME=${NM_DOMAIN}
       - SERVER_NAME=${NM_DOMAIN}
       - SERVER_API_CONN_STRING=api.${NM_DOMAIN}:443
       - SERVER_API_CONN_STRING=api.${NM_DOMAIN}:443

+ 4 - 2
config/config.go

@@ -82,10 +82,12 @@ type ServerConfig struct {
 	TurnPassword               string `yaml:"turn_password"`
 	TurnPassword               string `yaml:"turn_password"`
 	UseTurn                    bool   `yaml:"use_turn"`
 	UseTurn                    bool   `yaml:"use_turn"`
 	UsersLimit                 int    `yaml:"user_limit"`
 	UsersLimit                 int    `yaml:"user_limit"`
-	ClientsLimit               int    `yaml:"client_limit"`
 	NetworksLimit              int    `yaml:"network_limit"`
 	NetworksLimit              int    `yaml:"network_limit"`
-	HostsLimit                 int    `yaml:"host_limit"`
+	MachinesLimit              int    `yaml:"machines_limit"`
+	IngressesLimit             int    `yaml:"ingresses_limit"`
+	EgressesLimit              int    `yaml:"egresses_limit"`
 	DeployedByOperator         bool   `yaml:"deployed_by_operator"`
 	DeployedByOperator         bool   `yaml:"deployed_by_operator"`
+	Environment                string `yaml:"environment"`
 }
 }
 
 
 // SQLConfig - Generic SQL Config
 // SQLConfig - Generic SQL Config

+ 7 - 0
controllers/controller.go

@@ -14,6 +14,9 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
+// HttpMiddlewares - middleware functions for REST interactions
+var HttpMiddlewares []mux.MiddlewareFunc
+
 // HttpHandlers - handler functions for REST interactions
 // HttpHandlers - handler functions for REST interactions
 var HttpHandlers = []interface{}{
 var HttpHandlers = []interface{}{
 	nodeHandlers,
 	nodeHandlers,
@@ -42,6 +45,10 @@ func HandleRESTRequests(wg *sync.WaitGroup, ctx context.Context) {
 	originsOk := handlers.AllowedOrigins(strings.Split(servercfg.GetAllowedOrigin(), ","))
 	originsOk := handlers.AllowedOrigins(strings.Split(servercfg.GetAllowedOrigin(), ","))
 	methodsOk := handlers.AllowedMethods([]string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete})
 	methodsOk := handlers.AllowedMethods([]string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete})
 
 
+	for _, middleware := range HttpMiddlewares {
+		r.Use(middleware)
+	}
+
 	for _, handler := range HttpHandlers {
 	for _, handler := range HttpHandlers {
 		handler.(func(*mux.Router))(r)
 		handler.(func(*mux.Router))(r)
 	}
 	}

+ 1 - 1
controllers/docs.go

@@ -10,7 +10,7 @@
 //
 //
 //	Schemes: https
 //	Schemes: https
 //	BasePath: /
 //	BasePath: /
-//	Version: 0.20.5
+//	Version: 0.20.6
 //	Host: netmaker.io
 //	Host: netmaker.io
 //
 //
 //	Consumes:
 //	Consumes:

+ 76 - 61
controllers/ext_client.go

@@ -17,6 +17,7 @@ import (
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/skip2/go-qrcode"
 	"github.com/skip2/go-qrcode"
+	"golang.org/x/exp/slog"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
@@ -28,7 +29,7 @@ func extClientHandlers(r *mux.Router) {
 	r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(getExtClientConf))).Methods(http.MethodGet)
 	r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(getExtClientConf))).Methods(http.MethodGet)
 	r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(updateExtClient))).Methods(http.MethodPut)
 	r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(updateExtClient))).Methods(http.MethodPut)
 	r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(deleteExtClient))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(deleteExtClient))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.NetUserSecurityCheck(false, true, checkFreeTierLimits(clients_l, http.HandlerFunc(createExtClient)))).Methods(http.MethodPost)
+	r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.NetUserSecurityCheck(false, true, checkFreeTierLimits(limitChoiceMachines, http.HandlerFunc(createExtClient)))).Methods(http.MethodPost)
 }
 }
 
 
 func checkIngressExists(nodeID string) bool {
 func checkIngressExists(nodeID string) bool {
@@ -216,7 +217,12 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	if network.DefaultKeepalive != 0 {
 	if network.DefaultKeepalive != 0 {
 		keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
 		keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
 	}
 	}
-	gwendpoint := host.EndpointIP.String() + ":" + strconv.Itoa(host.ListenPort)
+	gwendpoint := ""
+	if host.EndpointIP.To4() == nil {
+		gwendpoint = fmt.Sprintf("[%s]:%d", host.EndpointIP.String(), host.ListenPort)
+	} else {
+		gwendpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), host.ListenPort)
+	}
 	newAllowedIPs := network.AddressRange
 	newAllowedIPs := network.AddressRange
 	if newAllowedIPs != "" && network.AddressRange6 != "" {
 	if newAllowedIPs != "" && network.AddressRange6 != "" {
 		newAllowedIPs += ","
 		newAllowedIPs += ","
@@ -308,31 +314,28 @@ 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"]
 	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")
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to create extclient on network [%s]: %v", networkName, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		slog.Error("failed to create extclient", "user", r.Header.Get("user"), "error", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 
 
-	var extclient models.ExtClient
 	var customExtClient models.CustomExtClient
 	var customExtClient models.CustomExtClient
 
 
 	if err := json.NewDecoder(r.Body).Decode(&customExtClient); err != nil {
 	if err := json.NewDecoder(r.Body).Decode(&customExtClient); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
-	if err := validateExtClient(&extclient, &customExtClient); err != nil {
+	if err := validateCustomExtClient(&customExtClient, true); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
+	extclient := logic.UpdateExtClient(&models.ExtClient{}, &customExtClient)
 
 
-	extclient.Network = networkName
 	extclient.IngressGatewayID = nodeid
 	extclient.IngressGatewayID = nodeid
 	node, err := logic.GetNodeByID(nodeid)
 	node, err := logic.GetNodeByID(nodeid)
 	if err != nil {
 	if err != nil {
@@ -341,6 +344,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
+	extclient.Network = node.Network
 	host, err := logic.GetHost(node.HostID.String())
 	host, err := logic.GetHost(node.HostID.String())
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
@@ -351,21 +355,19 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	listenPort := logic.GetPeerListenPort(host)
 	listenPort := logic.GetPeerListenPort(host)
 	extclient.IngressGatewayEndpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), listenPort)
 	extclient.IngressGatewayEndpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), listenPort)
 	extclient.Enabled = true
 	extclient.Enabled = true
-	parentNetwork, err := logic.GetNetwork(networkName)
+	parentNetwork, err := logic.GetNetwork(node.Network)
 	if err == nil { // check if parent network default ACL is enabled (yes) or not (no)
 	if err == nil { // check if parent network default ACL is enabled (yes) or not (no)
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 	}
 	}
 
 
 	if err := logic.SetClientDefaultACLs(&extclient); err != nil {
 	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))
+		slog.Error("failed to set default acls for extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
 
 
 	if err = logic.CreateExtClient(&extclient); err != nil {
 	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))
+		slog.Error("failed to create extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
@@ -374,13 +376,13 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	if r.Header.Get("ismaster") != "yes" {
 	if r.Header.Get("ismaster") != "yes" {
 		userID := r.Header.Get("user")
 		userID := r.Header.Get("user")
 		if isAdmin, err = checkProClientAccess(userID, extclient.ClientID, &parentNetwork); err != nil {
 		if isAdmin, err = checkProClientAccess(userID, extclient.ClientID, &parentNetwork); err != nil {
-			logger.Log(0, userID, "attempted to create a client on network", networkName, "but they lack access")
-			logic.DeleteExtClient(networkName, extclient.ClientID)
+			slog.Error("pro client access check failed", "user", userID, "network", node.Network, "error", err)
+			logic.DeleteExtClient(node.Network, extclient.ClientID)
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 			return
 			return
 		}
 		}
 		if !isAdmin {
 		if !isAdmin {
-			if err = pro.AssociateNetworkUserClient(userID, networkName, extclient.ClientID); err != nil {
+			if err = pro.AssociateNetworkUserClient(userID, node.Network, extclient.ClientID); err != nil {
 				logger.Log(0, "failed to associate client", extclient.ClientID, "to user", userID)
 				logger.Log(0, "failed to associate client", extclient.ClientID, "to user", userID)
 			}
 			}
 			extclient.OwnerID = userID
 			extclient.OwnerID = userID
@@ -390,7 +392,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}
 	}
 
 
-	logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
+	slog.Info("created extclient", "user", r.Header.Get("user"), "network", node.Network, "clientid", extclient.ClientID)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	go func() {
 	go func() {
 		if err := mq.PublishPeerUpdate(); err != nil {
 		if err := mq.PublishPeerUpdate(); err != nil {
@@ -419,7 +421,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 
 
 	var update models.CustomExtClient
 	var update models.CustomExtClient
-	var oldExtClient models.ExtClient
+	//var oldExtClient models.ExtClient
 	var sendPeerUpdate bool
 	var sendPeerUpdate bool
 	err := json.NewDecoder(r.Body).Decode(&update)
 	err := json.NewDecoder(r.Body).Decode(&update)
 	if err != nil {
 	if err != nil {
@@ -429,50 +431,40 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	clientid := params["clientid"]
 	clientid := params["clientid"]
-	network := params["network"]
-	key, err := logic.GetRecordKey(clientid, network)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to get record key for client [%s], network [%s]: %v",
-				clientid, network, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	if err := validateExtClient(&oldExtClient, &update); err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	data, err := database.FetchRecord(database.EXT_CLIENT_TABLE_NAME, key)
+	oldExtClient, err := logic.GetExtClientByName(clientid)
 	if err != nil {
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to fetch  ext client record key [%s] from db for client [%s], network [%s]: %v",
-				key, clientid, network, err))
+		slog.Error("failed to retrieve extclient", "user", r.Header.Get("user"), "id", clientid, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	if err = json.Unmarshal([]byte(data), &oldExtClient); err != nil {
-		logger.Log(0, "error unmarshalling extclient: ",
-			err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
+	if oldExtClient.ClientID == update.ClientID {
+		if err := validateCustomExtClient(&update, false); err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
+	} else {
+		if err := validateCustomExtClient(&update, true); err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
 	}
 	}
 
 
 	// == PRO ==
 	// == PRO ==
-	networkName := params["network"]
+	//networkName := params["network"]
 	var changedID = update.ClientID != oldExtClient.ClientID
 	var changedID = update.ClientID != oldExtClient.ClientID
 	if r.Header.Get("ismaster") != "yes" {
 	if r.Header.Get("ismaster") != "yes" {
 		userID := r.Header.Get("user")
 		userID := r.Header.Get("user")
-		_, doesOwn := doesUserOwnClient(userID, params["clientid"], networkName)
+		_, doesOwn := doesUserOwnClient(userID, params["clientid"], oldExtClient.Network)
 		if !doesOwn {
 		if !doesOwn {
 			logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "internal"))
 			logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("user not permitted"), "internal"))
 			return
 			return
 		}
 		}
 	}
 	}
 	if changedID && oldExtClient.OwnerID != "" {
 	if changedID && oldExtClient.OwnerID != "" {
-		if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, networkName, oldExtClient.ClientID); err != nil {
+		if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, oldExtClient.Network, oldExtClient.ClientID); err != nil {
 			logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
 			logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
 		}
 		}
-		if err := pro.AssociateNetworkUserClient(oldExtClient.OwnerID, networkName, update.ClientID); err != nil {
+		if err := pro.AssociateNetworkUserClient(oldExtClient.OwnerID, oldExtClient.Network, update.ClientID); err != nil {
 			logger.Log(0, "failed to associate client", update.ClientID, "to user", oldExtClient.OwnerID)
 			logger.Log(0, "failed to associate client", update.ClientID, "to user", oldExtClient.OwnerID)
 		}
 		}
 	}
 	}
@@ -485,13 +477,15 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	if update.Enabled != oldExtClient.Enabled {
 	if update.Enabled != oldExtClient.Enabled {
 		sendPeerUpdate = true
 		sendPeerUpdate = true
 	}
 	}
-	// extra var need as logic.Update changes oldExtClient
-	currentClient := oldExtClient
-	newclient, err := logic.UpdateExtClient(&oldExtClient, &update)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to update ext client [%s], network [%s]: %v",
-				clientid, network, err))
+	newclient := logic.UpdateExtClient(&oldExtClient, &update)
+	if err := logic.DeleteExtClient(oldExtClient.Network, oldExtClient.ClientID); err != nil {
+
+		slog.Error("failed to delete ext client", "user", r.Header.Get("user"), "id", oldExtClient.ClientID, "network", oldExtClient.Network, "error", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	if err := logic.SaveExtClient(&newclient); err != nil {
+		slog.Error("failed to save ext client", "user", r.Header.Get("user"), "id", newclient.ClientID, "network", newclient.Network, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
@@ -507,7 +501,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(newclient)
 	json.NewEncoder(w).Encode(newclient)
 	if changedID {
 	if changedID {
 		go func() {
 		go func() {
-			if err := mq.PublishExtClientDNSUpdate(currentClient, *newclient, networkName); err != nil {
+			if err := mq.PublishExtClientDNSUpdate(oldExtClient, newclient, oldExtClient.Network); err != nil {
 				logger.Log(1, "error pubishing dns update for extcient update", err.Error())
 				logger.Log(1, "error pubishing dns update for extcient update", err.Error())
 			}
 			}
 		}()
 		}()
@@ -647,18 +641,20 @@ func doesUserOwnClient(username, clientID, network string) (bool, bool) {
 	return false, logic.StringSliceContains(netUser.Clients, clientID)
 	return false, logic.StringSliceContains(netUser.Clients, clientID)
 }
 }
 
 
-// validateExtClient	Validates the extclient object
-func validateExtClient(extclient *models.ExtClient, customExtClient *models.CustomExtClient) error {
+// validateCustomExtClient	Validates the extclient object
+func validateCustomExtClient(customExtClient *models.CustomExtClient, checkID bool) error {
 	//validate clientid
 	//validate clientid
-	if customExtClient.ClientID != "" && !validName(customExtClient.ClientID) {
-		return errInvalidExtClientID
+	if customExtClient.ClientID != "" {
+		if err := isValid(customExtClient.ClientID, checkID); err != nil {
+			return fmt.Errorf("client validatation: %v", err)
+		}
 	}
 	}
-	extclient.ClientID = customExtClient.ClientID
+	//extclient.ClientID = customExtClient.ClientID
 	if len(customExtClient.PublicKey) > 0 {
 	if len(customExtClient.PublicKey) > 0 {
 		if _, err := wgtypes.ParseKey(customExtClient.PublicKey); err != nil {
 		if _, err := wgtypes.ParseKey(customExtClient.PublicKey); err != nil {
 			return errInvalidExtClientPubKey
 			return errInvalidExtClientPubKey
 		}
 		}
-		extclient.PublicKey = customExtClient.PublicKey
+		//extclient.PublicKey = customExtClient.PublicKey
 	}
 	}
 	//validate extra ips
 	//validate extra ips
 	if len(customExtClient.ExtraAllowedIPs) > 0 {
 	if len(customExtClient.ExtraAllowedIPs) > 0 {
@@ -667,14 +663,33 @@ func validateExtClient(extclient *models.ExtClient, customExtClient *models.Cust
 				return errInvalidExtClientExtraIP
 				return errInvalidExtClientExtraIP
 			}
 			}
 		}
 		}
-		extclient.ExtraAllowedIPs = customExtClient.ExtraAllowedIPs
+		//extclient.ExtraAllowedIPs = customExtClient.ExtraAllowedIPs
 	}
 	}
 	//validate DNS
 	//validate DNS
 	if customExtClient.DNS != "" {
 	if customExtClient.DNS != "" {
 		if ip := net.ParseIP(customExtClient.DNS); ip == nil {
 		if ip := net.ParseIP(customExtClient.DNS); ip == nil {
 			return errInvalidExtClientDNS
 			return errInvalidExtClientDNS
 		}
 		}
-		extclient.DNS = customExtClient.DNS
+		//extclient.DNS = customExtClient.DNS
+	}
+	return nil
+}
+
+// isValid	Checks if the clientid is valid
+func isValid(clientid string, checkID bool) error {
+	if !validName(clientid) {
+		return errInvalidExtClientID
+	}
+	if checkID {
+		extclients, err := logic.GetAllExtClients()
+		if err != nil {
+			return fmt.Errorf("extclients isValid: %v", err)
+		}
+		for _, extclient := range extclients {
+			if clientid == extclient.ClientID {
+				return errDuplicateExtClientName
+			}
+		}
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 45 - 3
controllers/hosts.go

@@ -13,12 +13,14 @@ import (
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/bcrypt"
+	"golang.org/x/exp/slog"
 )
 )
 
 
 func hostHandlers(r *mux.Router) {
 func hostHandlers(r *mux.Router) {
 	r.HandleFunc("/api/hosts", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
 	r.HandleFunc("/api/hosts", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
 	r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))).Methods(http.MethodPut)
+	r.HandleFunc("/api/hosts/{hostid}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHost))).Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(addHostToNetwork))).Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(addHostToNetwork))).Methods(http.MethodPost)
@@ -326,13 +328,13 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 		// unset all the relayed nodes
 		// unset all the relayed nodes
 		logic.SetRelayedNodes(false, node.ID.String(), node.RelayedNodes)
 		logic.SetRelayedNodes(false, node.ID.String(), node.RelayedNodes)
 	}
 	}
-	node.Action = models.NODE_DELETE
-	node.PendingDelete = true
-	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
+	logger.Log(1, "deleting node", node.ID.String(), "from host", currHost.Name)
 	if err := logic.DeleteNode(node, forceDelete); err != nil {
 	if err := logic.DeleteNode(node, forceDelete); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 		return
 	}
 	}
+	node.Action = models.NODE_DELETE
+	node.PendingDelete = true
 	// notify node change
 	// notify node change
 	runUpdates(node, false)
 	runUpdates(node, false)
 	go func() { // notify of peer change
 	go func() { // notify of peer change
@@ -583,3 +585,43 @@ func updateKeys(w http.ResponseWriter, r *http.Request) {
 	logger.Log(2, r.Header.Get("user"), "updated key on host", host.Name)
 	logger.Log(2, r.Header.Get("user"), "updated key on host", host.Name)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 }
 }
+
+// swagger:route POST /api/hosts/{hostId}/sync host syncHost
+//
+// Requests a host to pull.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: networkBodyResponse
+func syncHost(w http.ResponseWriter, r *http.Request) {
+	hostId := mux.Vars(r)["hostid"]
+
+	var errorResponse = models.ErrorResponse{}
+	w.Header().Set("Content-Type", "application/json")
+
+	host, err := logic.GetHost(hostId)
+	if err != nil {
+		slog.Error("failed to retrieve host", "user", r.Header.Get("user"), "error", err)
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = err.Error()
+		logic.ReturnErrorResponse(w, r, errorResponse)
+		return
+	}
+
+	go func() {
+		hostUpdate := models.HostUpdate{
+			Action: models.RequestPull,
+			Host:   *host,
+		}
+		if err = mq.HostUpdate(&hostUpdate); err != nil {
+			slog.Error("failed to send host pull request", "host", host.ID.String(), "error", err)
+		}
+	}()
+
+	slog.Info("requested host pull", "user", r.Header.Get("user"), "host", host.ID)
+	w.WriteHeader(http.StatusOK)
+}

+ 40 - 16
controllers/limits.go

@@ -10,36 +10,60 @@ import (
 
 
 // limit consts
 // limit consts
 const (
 const (
-	node_l     = 0
-	networks_l = 1
-	users_l    = 2
-	clients_l  = 3
+	limitChoiceNetworks = iota
+	limitChoiceUsers
+	limitChoiceMachines
+	limitChoiceIngress
+	limitChoiceEgress
 )
 )
 
 
-func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc {
+func checkFreeTierLimits(limitChoice int, 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.StatusForbidden, Message: "free tier limits exceeded on networks",
+			Code: http.StatusForbidden, Message: "free tier limits exceeded on ",
 		}
 		}
 
 
-		if logic.Free_Tier { // check that free tier limits not exceeded
-			if limit_choice == networks_l {
+		if logic.FreeTier { // check that free tier limits not exceeded
+			switch limitChoice {
+			case limitChoiceNetworks:
 				currentNetworks, err := logic.GetNetworks()
 				currentNetworks, err := logic.GetNetworks()
-				if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= logic.Networks_Limit {
+				if (err != nil && !database.IsEmptyRecord(err)) ||
+					len(currentNetworks) >= logic.NetworksLimit {
+					errorResponse.Message += "networks"
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					return
 					return
 				}
 				}
-			} else if limit_choice == users_l {
+			case limitChoiceUsers:
 				users, err := logic.GetUsers()
 				users, err := logic.GetUsers()
-				if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit {
-					errorResponse.Message = "free tier limits exceeded on users"
+				if (err != nil && !database.IsEmptyRecord(err)) ||
+					len(users) >= logic.UsersLimit {
+					errorResponse.Message += "users"
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					return
 					return
 				}
 				}
-			} else if limit_choice == clients_l {
-				clients, err := logic.GetAllExtClients()
-				if (err != nil && !database.IsEmptyRecord(err)) || len(clients) >= logic.Clients_Limit {
-					errorResponse.Message = "free tier limits exceeded on external clients"
+			case limitChoiceMachines:
+				hosts, hErr := logic.GetAllHosts()
+				clients, cErr := logic.GetAllExtClients()
+				if (hErr != nil && !database.IsEmptyRecord(hErr)) ||
+					(cErr != nil && !database.IsEmptyRecord(cErr)) ||
+					len(hosts)+len(clients) >= logic.MachinesLimit {
+					errorResponse.Message += "machines"
+					logic.ReturnErrorResponse(w, r, errorResponse)
+					return
+				}
+			case limitChoiceIngress:
+				ingresses, err := logic.GetAllIngresses()
+				if (err != nil && !database.IsEmptyRecord(err)) ||
+					len(ingresses) >= logic.IngressesLimit {
+					errorResponse.Message += "ingresses"
+					logic.ReturnErrorResponse(w, r, errorResponse)
+					return
+				}
+			case limitChoiceEgress:
+				egresses, err := logic.GetAllEgresses()
+				if (err != nil && !database.IsEmptyRecord(err)) ||
+					len(egresses) >= logic.EgressesLimit {
+					errorResponse.Message += "egresses"
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					return
 					return
 				}
 				}

+ 169 - 43
controllers/migrate.go

@@ -2,15 +2,22 @@ package controller
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"fmt"
+	"net"
 	"net/http"
 	"net/http"
+	"strconv"
+	"time"
 
 
-	"github.com/gravitl/netmaker/auth"
+	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/bcrypt"
+	"golang.org/x/exp/slog"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
 // swagger:route PUT /api/v1/nodes/migrate nodes migrateNode
 // swagger:route PUT /api/v1/nodes/migrate nodes migrateNode
@@ -26,63 +33,182 @@ import (
 //				200: nodeJoinResponse
 //				200: nodeJoinResponse
 func migrate(w http.ResponseWriter, r *http.Request) {
 func migrate(w http.ResponseWriter, r *http.Request) {
 	data := models.MigrationData{}
 	data := models.MigrationData{}
+	host := models.Host{}
+	node := models.Node{}
+	nodes := []models.Node{}
+	server := models.ServerConfig{}
 	err := json.NewDecoder(r.Body).Decode(&data)
 	err := json.NewDecoder(r.Body).Decode(&data)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
 		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
-
-	var networksToAdd = []string{}
-	for i := range data.LegacyNodes {
-		legacyNode := data.LegacyNodes[i]
-		record, err := database.FetchRecord(database.NODES_TABLE_NAME, legacyNode.ID)
+	for i, legacy := range data.LegacyNodes {
+		record, err := database.FetchRecord(database.NODES_TABLE_NAME, legacy.ID)
 		if err != nil {
 		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())
+			slog.Error("legacy node not found", "error", err)
+			logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("legacy node not found %w", err), "badrequest"))
+			return
+		}
+		var legacyNode models.LegacyNode
+		if err = json.Unmarshal([]byte(record), &legacyNode); err != nil {
+			slog.Error("decoding legacy node", "errror", err)
+			logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("decode legacy node %w", err), "badrequest"))
+			return
+		}
+		if err := bcrypt.CompareHashAndPassword([]byte(legacyNode.Password), []byte(legacy.Password)); err != nil {
+			slog.Error("legacy node invalid password", "error", err)
+			logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("invalid password %w", err), "unauthorized"))
+			return
+		}
+		if i == 0 {
+			host, node = convertLegacyHostNode(legacy)
+			host.Name = data.HostName
+			host.HostPass = data.Password
+			host.OS = data.OS
+			if err := logic.CreateHost(&host); err != nil {
+				slog.Error("create host", "error", err)
 				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-				continue
+				return
+			}
+			server = servercfg.GetServerInfo()
+			if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+				server.MQUserName = host.ID.String()
 			}
 			}
-			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
+			key, keyErr := logic.RetrievePublicTrafficKey()
+			if keyErr != nil {
+				slog.Error("retrieving traffickey", "error", err)
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+				return
 			}
 			}
-			networksToAdd = append(networksToAdd, oldLegacyNode.Network)
-			_ = database.DeleteRecord(database.NODES_TABLE_NAME, oldLegacyNode.ID)
+			server.TrafficKey = key
+		} else {
+			node = convertLegacyNode(legacyNode, host.ID)
 		}
 		}
-	}
-	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
-	}
-	if !logic.HostExists(&data.NewHost) {
-		logic.CheckHostPorts(&data.NewHost)
-		if err = logic.CreateHost(&data.NewHost); err != nil {
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-			return
+		if err := logic.UpsertNode(&node); err != nil {
+			slog.Error("update node", "error", err)
+			continue
 		}
 		}
+		host.Nodes = append(host.Nodes, node.ID.String())
+
+		nodes = append(nodes, node)
 	}
 	}
-	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 err := logic.UpsertHost(&host); err != nil {
+		slog.Error("save host", "error", err)
 	}
 	}
-	server := servercfg.GetServerInfo()
-	server.TrafficKey = key
-	response := models.RegisterResponse{
-		ServerConf:    server,
-		RequestedHost: data.NewHost,
+	go mq.PublishPeerUpdate()
+	response := models.HostPull{
+		Host:         host,
+		Nodes:        nodes,
+		ServerConfig: server,
 	}
 	}
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(&response)
 	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 auth.CheckNetRegAndHostUpdate(networksToAdd, &data.NewHost)
+
+	slog.Info("migrated nodes")
+	// check for gateways
+	for _, node := range data.LegacyNodes {
+		if node.IsEgressGateway == "yes" {
+			egressGateway := models.EgressGatewayRequest{
+				NodeID:     node.ID,
+				Ranges:     node.EgressGatewayRanges,
+				NatEnabled: node.EgressGatewayNatEnabled,
+			}
+			if _, err := logic.CreateEgressGateway(egressGateway); err != nil {
+				logger.Log(0, "error creating egress gateway for node", node.ID, err.Error())
+			}
+		}
+		if node.IsIngressGateway == "yes" {
+			ingressGateway := models.IngressRequest{}
+			ingressNode, err := logic.CreateIngressGateway(node.Network, node.ID, ingressGateway)
+			if err != nil {
+				logger.Log(0, "error creating ingress gateway for node", node.ID, err.Error())
+			}
+			runUpdates(&ingressNode, true)
+		}
+	}
+}
+
+func convertLegacyHostNode(legacy models.LegacyNode) (models.Host, models.Node) {
+	//convert host
+	host := models.Host{}
+	host.ID = uuid.New()
+	host.IPForwarding = models.ParseBool(legacy.IPForwarding)
+	host.AutoUpdate = servercfg.AutoUpdateEnabled()
+	host.Interface = "netmaker"
+	host.ListenPort = int(legacy.ListenPort)
+	host.MTU = int(legacy.MTU)
+	host.PublicKey, _ = wgtypes.ParseKey(legacy.PublicKey)
+	host.MacAddress = net.HardwareAddr(legacy.MacAddress)
+	host.TrafficKeyPublic = legacy.TrafficKeys.Mine
+	host.Nodes = append([]string{}, legacy.ID)
+	host.Interfaces = legacy.Interfaces
+	//host.DefaultInterface = legacy.Defaul
+	host.EndpointIP = net.ParseIP(legacy.Endpoint)
+	host.IsDocker = models.ParseBool(legacy.IsDocker)
+	host.IsK8S = models.ParseBool(legacy.IsK8S)
+	host.IsStatic = models.ParseBool(legacy.IsStatic)
+	node := convertLegacyNode(legacy, host.ID)
+	return host, node
+}
+
+func convertLegacyNode(legacy models.LegacyNode, hostID uuid.UUID) models.Node {
+	//convert node
+	node := models.Node{}
+	node.ID, _ = uuid.Parse(legacy.ID)
+	node.HostID = hostID
+	node.Network = legacy.Network
+	valid4 := true
+	valid6 := true
+	_, cidr4, err := net.ParseCIDR(legacy.NetworkSettings.AddressRange)
+	if err != nil {
+		valid4 = false
+		slog.Warn("parsing address range", "error", err)
+	} else {
+		node.NetworkRange = *cidr4
+	}
+	_, cidr6, err := net.ParseCIDR(legacy.NetworkSettings.AddressRange6)
+	if err != nil {
+		valid6 = false
+		slog.Warn("parsing address range6", "error", err)
+	} else {
+		node.NetworkRange6 = *cidr6
+	}
+	node.Server = servercfg.GetServer()
+	node.Connected = models.ParseBool(legacy.Connected)
+	if valid4 {
+		node.Address = net.IPNet{
+			IP:   net.ParseIP(legacy.Address),
+			Mask: cidr4.Mask,
+		}
+	}
+	if valid6 {
+		node.Address6 = net.IPNet{
+			IP:   net.ParseIP(legacy.Address6),
+			Mask: cidr6.Mask,
+		}
+	}
+	node.Action = models.NODE_NOOP
+	node.LocalAddress = net.IPNet{
+		IP: net.ParseIP(legacy.LocalAddress),
+	}
+	node.IsEgressGateway = models.ParseBool(legacy.IsEgressGateway)
+	node.EgressGatewayRanges = legacy.EgressGatewayRanges
+	node.IsIngressGateway = models.ParseBool(legacy.IsIngressGateway)
+	node.IsRelayed = false
+	node.IsRelay = false
+	node.RelayedNodes = []string{}
+	node.DNSOn = models.ParseBool(legacy.DNSOn)
+	node.PersistentKeepalive = time.Duration(int64(time.Second) * int64(legacy.PersistentKeepalive))
+	node.LastModified = time.Now()
+	node.ExpirationDateTime, _ = time.Parse(strconv.Itoa(int(legacy.ExpirationDateTime)), "0")
+	node.EgressGatewayNatEnabled = models.ParseBool(legacy.EgressGatewayNatEnabled)
+	node.EgressGatewayRequest = legacy.EgressGatewayRequest
+	node.IngressGatewayRange = legacy.IngressGatewayRange
+	node.IngressGatewayRange6 = legacy.IngressGatewayRange6
+	node.DefaultACL = legacy.DefaultACL
+	node.OwnerID = legacy.OwnerID
+	node.FailoverNode, _ = uuid.Parse(legacy.FailoverNode)
+	node.Failover = models.ParseBool(legacy.Failover)
+	return node
 }
 }

+ 9 - 1
controllers/network.go

@@ -21,7 +21,7 @@ import (
 
 
 func networkHandlers(r *mux.Router) {
 func networkHandlers(r *mux.Router) {
 	r.HandleFunc("/api/networks", logic.SecurityCheck(false, http.HandlerFunc(getNetworks))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks", logic.SecurityCheck(false, http.HandlerFunc(getNetworks))).Methods(http.MethodGet)
-	r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
+	r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceNetworks, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
@@ -245,6 +245,14 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
+	if len(network.NetID) > 32 {
+		err := errors.New("network name shouldn't exceed 32 characters")
+		logger.Log(0, r.Header.Get("user"), "failed to create network: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
 	if network.AddressRange == "" && network.AddressRange6 == "" {
 	if network.AddressRange == "" && network.AddressRange6 == "" {
 		err := errors.New("IPv4 or IPv6 CIDR required")
 		err := errors.New("IPv4 or IPv6 CIDR required")
 		logger.Log(0, r.Header.Get("user"), "failed to create network: ",
 		logger.Log(0, r.Header.Get("user"), "failed to create network: ",

+ 2 - 2
controllers/network_test.go

@@ -80,7 +80,7 @@ func TestDeleteNetwork(t *testing.T) {
 		err := logic.DeleteNetwork("skynet")
 		err := logic.DeleteNetwork("skynet")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
-	t.Run("NonExistantNetwork", func(t *testing.T) {
+	t.Run("NonExistentNetwork", func(t *testing.T) {
 		err := logic.DeleteNetwork("skynet")
 		err := logic.DeleteNetwork("skynet")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
@@ -151,7 +151,7 @@ func TestValidateNetwork(t *testing.T) {
 		{
 		{
 			testname: "NetIDTooLong",
 			testname: "NetIDTooLong",
 			network: models.Network{
 			network: models.Network{
-				NetID: "LongNetIDName",
+				NetID: "LongNetIDNameForMaxCharactersTest",
 			},
 			},
 			errMessage: "Field validation for 'NetID' failed on the 'max' tag",
 			errMessage: "Field validation for 'NetID' failed on the 'max' tag",
 		},
 		},

+ 49 - 27
controllers/node.go

@@ -16,6 +16,7 @@ import (
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/bcrypt"
+	"golang.org/x/exp/slog"
 )
 )
 
 
 var hostIDHeader = "host-id"
 var hostIDHeader = "host-id"
@@ -27,9 +28,9 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).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}", Authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", Authorize(false, true, "user", http.HandlerFunc(createEgressGateway))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", Authorize(false, true, "user", checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", Authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", Authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, http.HandlerFunc(createIngressGateway))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
@@ -373,11 +374,10 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 
 
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
-	node, err := logic.GetNodeByID(nodeid)
+
+	node, err := validateParams(nodeid, params["network"])
 	if err != nil {
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("error fetching node [ %s ] info: %v", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	host, err := logic.GetHost(node.HostID.String())
 	host, err := logic.GetHost(node.HostID.String())
@@ -442,16 +442,20 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 	var gateway models.EgressGatewayRequest
 	var gateway models.EgressGatewayRequest
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
-	w.Header().Set("Content-Type", "application/json")
-	err := json.NewDecoder(r.Body).Decode(&gateway)
+	node, err := validateParams(params["nodeid"], params["network"])
 	if err != nil {
 	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewDecoder(r.Body).Decode(&gateway); err != nil {
 		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
 		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	gateway.NetID = params["network"]
 	gateway.NetID = params["network"]
 	gateway.NodeID = params["nodeid"]
 	gateway.NodeID = params["nodeid"]
-	node, err := logic.CreateEgressGateway(gateway)
+	node, err = logic.CreateEgressGateway(gateway)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to create egress gateway on node [%s] on network [%s]: %v",
 			fmt.Sprintf("failed to create egress gateway on node [%s] on network [%s]: %v",
@@ -487,7 +491,12 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
 	netid := params["network"]
 	netid := params["network"]
-	node, err := logic.DeleteEgressGateway(netid, nodeid)
+	node, err := validateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		return
+	}
+	node, err = logic.DeleteEgressGateway(netid, nodeid)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to delete egress gateway on node [%s] on network [%s]: %v",
 			fmt.Sprintf("failed to delete egress gateway on node [%s] on network [%s]: %v",
@@ -524,10 +533,14 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
 	netid := params["network"]
 	netid := params["network"]
+	node, err := validateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		return
+	}
 	var request models.IngressRequest
 	var request models.IngressRequest
 	json.NewDecoder(r.Body).Decode(&request)
 	json.NewDecoder(r.Body).Decode(&request)
-
-	node, err := logic.CreateIngressGateway(netid, nodeid, request)
+	node, err = logic.CreateIngressGateway(netid, nodeid, request)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to create ingress gateway on node [%s] on network [%s]: %v",
 			fmt.Sprintf("failed to create ingress gateway on node [%s] on network [%s]: %v",
@@ -566,6 +579,11 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
 	netid := params["network"]
 	netid := params["network"]
+	node, err := validateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		return
+	}
 	node, wasFailover, removedClients, err := logic.DeleteIngressGateway(nodeid)
 	node, wasFailover, removedClients, err := logic.DeleteIngressGateway(nodeid)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
@@ -623,14 +641,11 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 
 
 	//start here
 	//start here
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
-	currentNode, err := logic.GetNodeByID(nodeid)
+	currentNode, err := validateParams(nodeid, params["network"])
 	if err != nil {
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("error fetching node [ %s ] info: %v", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
 		return
 		return
 	}
 	}
-
 	var newData models.ApiNode
 	var newData models.ApiNode
 	// we decode our body request params
 	// we decode our body request params
 	err = json.NewDecoder(r.Body).Decode(&newData)
 	err = json.NewDecoder(r.Body).Decode(&newData)
@@ -721,19 +736,13 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	// get params
 	// get params
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	var nodeid = params["nodeid"]
 	var nodeid = params["nodeid"]
-	forceDelete := r.URL.Query().Get("force") == "true"
-	fromNode := r.Header.Get("requestfrom") == "node"
-	node, err := logic.GetNodeByID(nodeid)
+	node, err := validateParams(nodeid, params["network"])
 	if err != nil {
 	if err != nil {
-		if logic.CheckAndRemoveLegacyNode(nodeid) {
-			logger.Log(0, "removed legacy node", nodeid)
-			logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
-		} else {
-			logger.Log(0, "error retrieving node to delete", err.Error())
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		}
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
 		return
 		return
 	}
 	}
+	forceDelete := r.URL.Query().Get("force") == "true"
+	fromNode := r.Header.Get("requestfrom") == "node"
 	if r.Header.Get("ismaster") != "yes" {
 	if r.Header.Get("ismaster") != "yes" {
 		username := r.Header.Get("user")
 		username := r.Header.Get("user")
 		if username != "" && !doesUserOwnNode(username, params["network"], nodeid) {
 		if username != "" && !doesUserOwnNode(username, params["network"], nodeid) {
@@ -816,3 +825,16 @@ func doesUserOwnNode(username, network, nodeID string) bool {
 
 
 	return logic.StringSliceContains(netUser.Nodes, nodeID)
 	return logic.StringSliceContains(netUser.Nodes, nodeID)
 }
 }
+
+func validateParams(nodeid, netid string) (models.Node, error) {
+	node, err := logic.GetNodeByID(nodeid)
+	if err != nil {
+		slog.Error("error fetching node", "node", nodeid, "error", err.Error())
+		return node, fmt.Errorf("error fetching node during parameter validation: %v", err)
+	}
+	if node.Network != netid {
+		slog.Error("network url param does not match node id", "url nodeid", netid, "node", node.Network)
+		return node, fmt.Errorf("network url param does not match node network")
+	}
+	return node, nil
+}

+ 2 - 1
controllers/regex.go

@@ -10,6 +10,7 @@ var (
 	errInvalidExtClientID      = errors.New("ext client ID must be alphanumderic and/or dashes and less that 15 chars")
 	errInvalidExtClientID      = errors.New("ext client ID must be alphanumderic and/or dashes and less that 15 chars")
 	errInvalidExtClientExtraIP = errors.New("ext client extra ip must be a valid cidr")
 	errInvalidExtClientExtraIP = errors.New("ext client extra ip must be a valid cidr")
 	errInvalidExtClientDNS     = errors.New("ext client dns must be a valid ip address")
 	errInvalidExtClientDNS     = errors.New("ext client dns must be a valid ip address")
+	errDuplicateExtClientName  = errors.New("duplicate client name")
 )
 )
 
 
 // allow only dashes and alphaneumeric for ext client and node names
 // allow only dashes and alphaneumeric for ext client and node names
@@ -21,7 +22,7 @@ func validName(name string) bool {
 	if !reg.MatchString(name) {
 	if !reg.MatchString(name) {
 		return false
 		return false
 	}
 	}
-	if len(name) > 15 {
+	if len(name) < 5 || len(name) > 32 {
 		return false
 		return false
 	}
 	}
 	return true
 	return true

+ 17 - 3
controllers/regex_test.go

@@ -27,19 +27,33 @@ func TestValidName(t *testing.T) {
 			Want: false,
 			Want: false,
 		},
 		},
 		{
 		{
-			Name: "nametoolong",
+			Name: "longname",
 			Args: args{
 			Args: args{
 				Name: "TestvalidNameTestvalidName",
 				Name: "TestvalidNameTestvalidName",
 			},
 			},
-			Want: false,
+			Want: true,
 		},
 		},
 		{
 		{
-			Name: "maxlength",
+			Name: "max length",
 			Args: args{
 			Args: args{
 				Name: "123456789012345",
 				Name: "123456789012345",
 			},
 			},
 			Want: true,
 			Want: true,
 		},
 		},
+		{
+			Name: "min length",
+			Args: args{
+				Name: "ama",
+			},
+			Want: false,
+		},
+		{
+			Name: "toolong",
+			Args: args{
+				Name: "123456789012345123123123123123123123123123123",
+			},
+			Want: false,
+		},
 	}
 	}
 	for _, tt := range tests {
 	for _, tt := range tests {
 		t.Run(tt.Name, func(t *testing.T) {
 		t.Run(tt.Name, func(t *testing.T) {

+ 27 - 16
controllers/server.go

@@ -24,12 +24,16 @@ func serverHandlers(r *mux.Router) {
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
 }
 }
+
+// TODO move to EE package? there is a function and a type there for that already
 func getUsage(w http.ResponseWriter, r *http.Request) {
 func getUsage(w http.ResponseWriter, r *http.Request) {
 	type usage struct {
 	type usage struct {
-		Hosts    int `json:"hosts"`
-		Clients  int `json:"clients"`
-		Networks int `json:"networks"`
-		Users    int `json:"users"`
+		Hosts     int `json:"hosts"`
+		Clients   int `json:"clients"`
+		Networks  int `json:"networks"`
+		Users     int `json:"users"`
+		Ingresses int `json:"ingresses"`
+		Egresses  int `json:"egresses"`
 	}
 	}
 	var serverUsage usage
 	var serverUsage usage
 	hosts, err := logic.GetAllHosts()
 	hosts, err := logic.GetAllHosts()
@@ -48,6 +52,14 @@ func getUsage(w http.ResponseWriter, r *http.Request) {
 	if err == nil {
 	if err == nil {
 		serverUsage.Networks = len(networks)
 		serverUsage.Networks = len(networks)
 	}
 	}
+	ingresses, err := logic.GetAllIngresses()
+	if err == nil {
+		serverUsage.Ingresses = len(ingresses)
+	}
+	egresses, err := logic.GetAllEgresses()
+	if err == nil {
+		serverUsage.Egresses = len(egresses)
+	}
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 	json.NewEncoder(w).Encode(models.SuccessResponse{
 	json.NewEncoder(w).Encode(models.SuccessResponse{
 		Code:     http.StatusOK,
 		Code:     http.StatusOK,
@@ -68,22 +80,21 @@ func getUsage(w http.ResponseWriter, r *http.Request) {
 //			Responses:
 //			Responses:
 //				200: serverConfigResponse
 //				200: serverConfigResponse
 func getStatus(w http.ResponseWriter, r *http.Request) {
 func getStatus(w http.ResponseWriter, r *http.Request) {
-	// TODO
-	// - check health of broker
 	type status struct {
 	type status struct {
-		DB     bool `json:"db_connected"`
-		Broker bool `json:"broker_connected"`
-		Usage  struct {
-			Hosts    int `json:"hosts"`
-			Clients  int `json:"clients"`
-			Networks int `json:"networks"`
-			Users    int `json:"users"`
-		} `json:"usage"`
+		DB           bool   `json:"db_connected"`
+		Broker       bool   `json:"broker_connected"`
+		LicenseError string `json:"license_error"`
+	}
+
+	licenseErr := ""
+	if servercfg.ErrLicenseValidation != nil {
+		licenseErr = servercfg.ErrLicenseValidation.Error()
 	}
 	}
 
 
 	currentServerStatus := status{
 	currentServerStatus := status{
-		DB:     database.IsConnected(),
-		Broker: mq.IsConnected(),
+		DB:           database.IsConnected(),
+		Broker:       mq.IsConnected(),
+		LicenseError: licenseErr,
 	}
 	}
 
 
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")

+ 1 - 1
controllers/user.go

@@ -30,7 +30,7 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(updateUser)))).Methods(http.MethodPut)
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(updateUser)))).Methods(http.MethodPut)
 	r.HandleFunc("/api/users/networks/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods(http.MethodPut)
 	r.HandleFunc("/api/users/networks/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods(http.MethodPut)
 	r.HandleFunc("/api/users/{username}/adm", logic.SecurityCheck(true, http.HandlerFunc(updateUserAdm))).Methods(http.MethodPut)
 	r.HandleFunc("/api/users/{username}/adm", logic.SecurityCheck(true, http.HandlerFunc(updateUserAdm))).Methods(http.MethodPut)
-	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(users_l, http.HandlerFunc(createUser)))).Methods(http.MethodPost)
+	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceUsers, http.HandlerFunc(createUser)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)

+ 17 - 0
ee/ee_controllers/middleware.go

@@ -0,0 +1,17 @@
+package ee_controllers
+
+import (
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/servercfg"
+	"net/http"
+)
+
+func OnlyServerAPIWhenUnlicensedMiddleware(handler http.Handler) http.Handler {
+	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+		if servercfg.ErrLicenseValidation != nil && request.URL.Path != "/api/server/status" {
+			logic.ReturnErrorResponse(writer, request, logic.FormatError(servercfg.ErrLicenseValidation, "forbidden"))
+			return
+		}
+		handler.ServeHTTP(writer, request)
+	})
+}

+ 11 - 4
ee/initialize.go

@@ -7,10 +7,10 @@ import (
 	controller "github.com/gravitl/netmaker/controllers"
 	controller "github.com/gravitl/netmaker/controllers"
 	"github.com/gravitl/netmaker/ee/ee_controllers"
 	"github.com/gravitl/netmaker/ee/ee_controllers"
 	eelogic "github.com/gravitl/netmaker/ee/logic"
 	eelogic "github.com/gravitl/netmaker/ee/logic"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/exp/slog"
 )
 )
 
 
 // InitEE - Initialize EE Logic
 // InitEE - Initialize EE Logic
@@ -18,6 +18,10 @@ func InitEE() {
 	setIsEnterprise()
 	setIsEnterprise()
 	servercfg.Is_EE = true
 	servercfg.Is_EE = true
 	models.SetLogo(retrieveEELogo())
 	models.SetLogo(retrieveEELogo())
+	controller.HttpMiddlewares = append(
+		controller.HttpMiddlewares,
+		ee_controllers.OnlyServerAPIWhenUnlicensedMiddleware,
+	)
 	controller.HttpHandlers = append(
 	controller.HttpHandlers = append(
 		controller.HttpHandlers,
 		controller.HttpHandlers,
 		ee_controllers.MetricHandlers,
 		ee_controllers.MetricHandlers,
@@ -27,8 +31,11 @@ func InitEE() {
 	)
 	)
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 		// == License Handling ==
 		// == License Handling ==
-		ValidateLicense()
-		logger.Log(0, "proceeding with Paid Tier license")
+		if err := ValidateLicense(); err != nil {
+			slog.Error(err.Error())
+			return
+		}
+		slog.Info("proceeding with Paid Tier license")
 		logic.SetFreeTierForTelemetry(false)
 		logic.SetFreeTierForTelemetry(false)
 		// == End License Handling ==
 		// == End License Handling ==
 		AddLicenseHooks()
 		AddLicenseHooks()
@@ -48,7 +55,7 @@ func resetFailover() {
 		for _, net := range nets {
 		for _, net := range nets {
 			err = eelogic.ResetFailover(net.NetID)
 			err = eelogic.ResetFailover(net.NetID)
 			if err != nil {
 			if err != nil {
-				logger.Log(0, "failed to reset failover on network", net.NetID, ":", err.Error())
+				slog.Error("failed to reset failover", "network", net.NetID, "error", err.Error())
 			}
 			}
 		}
 		}
 	}
 	}

+ 46 - 23
ee/license.go

@@ -12,7 +12,6 @@ import (
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
-	"os"
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
@@ -44,67 +43,85 @@ func AddLicenseHooks() {
 	}
 	}
 }
 }
 
 
-// ValidateLicense - the initial license check for netmaker server
+// ValidateLicense - the initial and periodic license check for netmaker server
 // checks if a license is valid + limits are not exceeded
 // checks if a license is valid + limits are not exceeded
-// if license is free_tier and limits exceeds, then server should terminate
-// if license is not valid, server should terminate
-func ValidateLicense() error {
+// if license is free_tier and limits exceeds, then function should error
+// if license is not valid, function should error
+func ValidateLicense() (err error) {
+	defer func() {
+		if err != nil {
+			err = fmt.Errorf("%w: %s", errValidation, err.Error())
+			servercfg.ErrLicenseValidation = err
+		}
+	}()
+
 	licenseKeyValue := servercfg.GetLicenseKey()
 	licenseKeyValue := servercfg.GetLicenseKey()
 	netmakerTenantID := servercfg.GetNetmakerTenantID()
 	netmakerTenantID := servercfg.GetNetmakerTenantID()
 	slog.Info("proceeding with Netmaker license validation...")
 	slog.Info("proceeding with Netmaker license validation...")
 	if len(licenseKeyValue) == 0 {
 	if len(licenseKeyValue) == 0 {
-		failValidation(errors.New("empty license-key (LICENSE_KEY environment variable)"))
+		err = errors.New("empty license-key (LICENSE_KEY environment variable)")
+		return err
 	}
 	}
 	if len(netmakerTenantID) == 0 {
 	if len(netmakerTenantID) == 0 {
-		failValidation(errors.New("empty tenant-id (NETMAKER_TENANT_ID environment variable)"))
+		err = errors.New("empty tenant-id (NETMAKER_TENANT_ID environment variable)")
+		return err
 	}
 	}
 
 
 	apiPublicKey, err := getLicensePublicKey(licenseKeyValue)
 	apiPublicKey, err := getLicensePublicKey(licenseKeyValue)
 	if err != nil {
 	if err != nil {
-		failValidation(fmt.Errorf("failed to get license public key: %w", err))
+		err = fmt.Errorf("failed to get license public key: %w", err)
+		return err
 	}
 	}
 
 
 	tempPubKey, tempPrivKey, err := FetchApiServerKeys()
 	tempPubKey, tempPrivKey, err := FetchApiServerKeys()
 	if err != nil {
 	if err != nil {
-		failValidation(fmt.Errorf("failed to fetch api server keys: %w", err))
+		err = fmt.Errorf("failed to fetch api server keys: %w", err)
+		return err
 	}
 	}
 
 
 	licenseSecret := LicenseSecret{
 	licenseSecret := LicenseSecret{
 		AssociatedID: netmakerTenantID,
 		AssociatedID: netmakerTenantID,
-		Limits:       getCurrentServerLimit(),
+		Usage:        getCurrentServerUsage(),
 	}
 	}
 
 
 	secretData, err := json.Marshal(&licenseSecret)
 	secretData, err := json.Marshal(&licenseSecret)
 	if err != nil {
 	if err != nil {
-		failValidation(fmt.Errorf("failed to marshal license secret: %w", err))
+		err = fmt.Errorf("failed to marshal license secret: %w", err)
+		return err
 	}
 	}
 
 
 	encryptedData, err := ncutils.BoxEncrypt(secretData, apiPublicKey, tempPrivKey)
 	encryptedData, err := ncutils.BoxEncrypt(secretData, apiPublicKey, tempPrivKey)
 	if err != nil {
 	if err != nil {
-		failValidation(fmt.Errorf("failed to encrypt license secret data: %w", err))
+		err = fmt.Errorf("failed to encrypt license secret data: %w", err)
+		return err
 	}
 	}
 
 
 	validationResponse, err := validateLicenseKey(encryptedData, tempPubKey)
 	validationResponse, err := validateLicenseKey(encryptedData, tempPubKey)
 	if err != nil {
 	if err != nil {
-		failValidation(fmt.Errorf("failed to validate license key: %w", err))
+		err = fmt.Errorf("failed to validate license key: %w", err)
+		return err
 	}
 	}
 	if len(validationResponse) == 0 {
 	if len(validationResponse) == 0 {
-		failValidation(errors.New("empty validation response"))
+		err = errors.New("empty validation response")
+		return err
 	}
 	}
 
 
 	var licenseResponse ValidatedLicense
 	var licenseResponse ValidatedLicense
 	if err = json.Unmarshal(validationResponse, &licenseResponse); err != nil {
 	if err = json.Unmarshal(validationResponse, &licenseResponse); err != nil {
-		failValidation(fmt.Errorf("failed to unmarshal validation response: %w", err))
+		err = fmt.Errorf("failed to unmarshal validation response: %w", err)
+		return err
 	}
 	}
 
 
 	respData, err := ncutils.BoxDecrypt(base64decode(licenseResponse.EncryptedLicense), apiPublicKey, tempPrivKey)
 	respData, err := ncutils.BoxDecrypt(base64decode(licenseResponse.EncryptedLicense), apiPublicKey, tempPrivKey)
 	if err != nil {
 	if err != nil {
-		failValidation(fmt.Errorf("failed to decrypt license: %w", err))
+		err = fmt.Errorf("failed to decrypt license: %w", err)
+		return err
 	}
 	}
 
 
 	license := LicenseKey{}
 	license := LicenseKey{}
 	if err = json.Unmarshal(respData, &license); err != nil {
 	if err = json.Unmarshal(respData, &license); err != nil {
-		failValidation(fmt.Errorf("failed to unmarshal license key: %w", err))
+		err = fmt.Errorf("failed to unmarshal license key: %w", err)
+		return err
 	}
 	}
 
 
 	slog.Info("License validation succeeded!")
 	slog.Info("License validation succeeded!")
@@ -158,11 +175,6 @@ func FetchApiServerKeys() (pub *[32]byte, priv *[32]byte, err error) {
 	return pub, priv, nil
 	return pub, priv, nil
 }
 }
 
 
-func failValidation(err error) {
-	slog.Error(errValidation.Error(), "error", err)
-	os.Exit(0)
-}
-
 func getLicensePublicKey(licensePubKeyEncoded string) (*[32]byte, error) {
 func getLicensePublicKey(licensePubKeyEncoded string) (*[32]byte, error) {
 	decodedPubKey := base64decode(licensePubKeyEncoded)
 	decodedPubKey := base64decode(licensePubKeyEncoded)
 	return ncutils.ConvertBytesToKey(decodedPubKey)
 	return ncutils.ConvertBytesToKey(decodedPubKey)
@@ -186,7 +198,7 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	req, err := http.NewRequest(http.MethodPost, api_endpoint, bytes.NewReader(requestBody))
+	req, err := http.NewRequest(http.MethodPost, getAccountsHost()+"/api/v1/license/validate", bytes.NewReader(requestBody))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -217,6 +229,17 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
 	return body, err
 	return body, err
 }
 }
 
 
+func getAccountsHost() string {
+	switch servercfg.GetEnvironment() {
+	case "dev":
+		return accountsHostDevelopment
+	case "staging":
+		return accountsHostStaging
+	default:
+		return accountsHostProduction
+	}
+}
+
 func cacheResponse(response []byte) error {
 func cacheResponse(response []byte) error {
 	var lrc = licenseResponseCache{
 	var lrc = licenseResponseCache{
 		Body: response,
 		Body: response,

+ 77 - 0
ee/license_test.go

@@ -0,0 +1,77 @@
+//go:build ee
+// +build ee
+
+package ee
+
+import (
+	"github.com/gravitl/netmaker/config"
+	"testing"
+)
+
+func Test_getAccountsHost(t *testing.T) {
+	tests := []struct {
+		name string
+		envK string
+		envV string
+		conf string
+		want string
+	}{
+		{
+			name: "no env var and no conf",
+			envK: "NOT_THE_CORRECT_ENV_VAR",
+			envV: "dev",
+			want: "https://api.accounts.netmaker.io",
+		},
+		{
+			name: "dev env var",
+			envK: "ENVIRONMENT",
+			envV: "dev",
+			want: "https://api.dev.accounts.netmaker.io",
+		},
+		{
+			name: "staging env var",
+			envK: "ENVIRONMENT",
+			envV: "staging",
+			want: "https://api.staging.accounts.netmaker.io",
+		},
+		{
+			name: "prod env var",
+			envK: "ENVIRONMENT",
+			envV: "prod",
+			want: "https://api.accounts.netmaker.io",
+		},
+		{
+			name: "dev conf",
+			conf: "dev",
+			want: "https://api.dev.accounts.netmaker.io",
+		},
+		{
+			name: "staging conf",
+			conf: "staging",
+			want: "https://api.staging.accounts.netmaker.io",
+		},
+		{
+			name: "prod conf",
+			conf: "prod",
+			want: "https://api.accounts.netmaker.io",
+		},
+		{
+			name: "env var vs conf precedence",
+			envK: "ENVIRONMENT",
+			envV: "prod",
+			conf: "staging",
+			want: "https://api.accounts.netmaker.io",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			config.Config.Server.Environment = tt.conf
+			if tt.envK != "" {
+				t.Setenv(tt.envK, tt.envV)
+			}
+			if got := getAccountsHost(); got != tt.want {
+				t.Errorf("getAccountsHost() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}

+ 39 - 22
ee/types.go

@@ -1,9 +1,20 @@
 package ee
 package ee
 
 
-import "fmt"
+import (
+	"fmt"
+)
+
+// constants for accounts api hosts
+const (
+	// accountsHostDevelopment is the accounts api host for development environment
+	accountsHostDevelopment = "https://api.dev.accounts.netmaker.io"
+	// accountsHostStaging is the accounts api host for staging environment
+	accountsHostStaging = "https://api.staging.accounts.netmaker.io"
+	// accountsHostProduction is the accounts api host for production environment
+	accountsHostProduction = "https://api.accounts.netmaker.io"
+)
 
 
 const (
 const (
-	api_endpoint               = "https://api.accounts.netmaker.io/api/v1/license/validate"
 	license_cache_key          = "license_response_cache"
 	license_cache_key          = "license_response_cache"
 	license_validation_err_msg = "invalid license"
 	license_validation_err_msg = "invalid license"
 	server_id_key              = "nm-server-id"
 	server_id_key              = "nm-server-id"
@@ -13,15 +24,17 @@ var errValidation = fmt.Errorf(license_validation_err_msg)
 
 
 // LicenseKey - the license key struct representation with associated data
 // LicenseKey - the license key struct representation with associated data
 type LicenseKey struct {
 type LicenseKey struct {
-	LicenseValue  string `json:"license_value"` // actual (public) key and the unique value for the key
-	Expiration    int64  `json:"expiration"`
-	LimitServers  int    `json:"limit_servers"`
-	LimitUsers    int    `json:"limit_users"`
-	LimitHosts    int    `json:"limit_hosts"`
-	LimitNetworks int    `json:"limit_networks"`
-	LimitClients  int    `json:"limit_clients"`
-	Metadata      string `json:"metadata"`
-	IsActive      bool   `json:"is_active"` // yes if active
+	LicenseValue   string `json:"license_value"` // actual (public) key and the unique value for the key
+	Expiration     int64  `json:"expiration"`
+	UsageServers   int    `json:"limit_servers"`
+	UsageUsers     int    `json:"limit_users"`
+	UsageClients   int    `json:"limit_clients"`
+	UsageHosts     int    `json:"limit_hosts"`
+	UsageNetworks  int    `json:"limit_networks"`
+	UsageIngresses int    `json:"limit_ingresses"`
+	UsageEgresses  int    `json:"limit_egresses"`
+	Metadata       string `json:"metadata"`
+	IsActive       bool   `json:"is_active"` // yes if active
 }
 }
 
 
 // ValidatedLicense - the validated license struct
 // ValidatedLicense - the validated license struct
@@ -32,26 +45,30 @@ type ValidatedLicense struct {
 
 
 // LicenseSecret - the encrypted struct for sending user-id
 // LicenseSecret - the encrypted struct for sending user-id
 type LicenseSecret struct {
 type LicenseSecret struct {
-	AssociatedID string        `json:"associated_id" binding:"required"` // UUID for user foreign key to User table
-	Limits       LicenseLimits `json:"limits" binding:"required"`
+	AssociatedID string `json:"associated_id" binding:"required"` // UUID for user foreign key to User table
+	Usage        Usage  `json:"limits" binding:"required"`
 }
 }
 
 
-// LicenseLimits - struct license limits
-type LicenseLimits struct {
-	Servers  int `json:"servers"`
-	Users    int `json:"users"`
-	Hosts    int `json:"hosts"`
-	Clients  int `json:"clients"`
-	Networks int `json:"networks"`
+// Usage - struct for license usage
+type Usage struct {
+	Servers   int `json:"servers"`
+	Users     int `json:"users"`
+	Hosts     int `json:"hosts"`
+	Clients   int `json:"clients"`
+	Networks  int `json:"networks"`
+	Ingresses int `json:"ingresses"`
+	Egresses  int `json:"egresses"`
 }
 }
 
 
-// LicenseLimits.SetDefaults - sets the default values for limits
-func (l *LicenseLimits) SetDefaults() {
+// Usage.SetDefaults - sets the default values for usage
+func (l *Usage) SetDefaults() {
 	l.Clients = 0
 	l.Clients = 0
 	l.Servers = 1
 	l.Servers = 1
 	l.Hosts = 0
 	l.Hosts = 0
 	l.Users = 1
 	l.Users = 1
 	l.Networks = 0
 	l.Networks = 0
+	l.Ingresses = 0
+	l.Egresses = 0
 }
 }
 
 
 // ValidateLicenseRequest - used for request to validate license endpoint
 // ValidateLicenseRequest - used for request to validate license endpoint

+ 14 - 5
ee/util.go

@@ -30,14 +30,15 @@ func base64decode(input string) []byte {
 
 
 	return bytes
 	return bytes
 }
 }
-func getCurrentServerLimit() (limits LicenseLimits) {
+
+func getCurrentServerUsage() (limits Usage) {
 	limits.SetDefaults()
 	limits.SetDefaults()
-	hosts, err := logic.GetAllHosts()
-	if err == nil {
+	hosts, hErr := logic.GetAllHosts()
+	if hErr == nil {
 		limits.Hosts = len(hosts)
 		limits.Hosts = len(hosts)
 	}
 	}
-	clients, err := logic.GetAllExtClients()
-	if err == nil {
+	clients, cErr := logic.GetAllExtClients()
+	if cErr == nil {
 		limits.Clients = len(clients)
 		limits.Clients = len(clients)
 	}
 	}
 	users, err := logic.GetUsers()
 	users, err := logic.GetUsers()
@@ -48,5 +49,13 @@ func getCurrentServerLimit() (limits LicenseLimits) {
 	if err == nil {
 	if err == nil {
 		limits.Networks = len(networks)
 		limits.Networks = len(networks)
 	}
 	}
+	ingresses, err := logic.GetAllIngresses()
+	if err == nil {
+		limits.Ingresses = len(ingresses)
+	}
+	egresses, err := logic.GetAllEgresses()
+	if err == nil {
+		limits.Egresses = len(egresses)
+	}
 	return
 	return
 }
 }

+ 6 - 6
go.mod

@@ -4,7 +4,7 @@ go 1.19
 
 
 require (
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.3
 	github.com/eclipse/paho.mqtt.golang v1.4.3
-	github.com/go-playground/validator/v10 v10.14.1
+	github.com/go-playground/validator/v10 v10.15.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.3.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/handlers v1.5.1
@@ -15,11 +15,11 @@ require (
 	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.4
 	github.com/stretchr/testify v1.8.4
 	github.com/txn2/txeh v1.4.0
 	github.com/txn2/txeh v1.4.0
-	golang.org/x/crypto v0.11.0
-	golang.org/x/net v0.12.0 // indirect
-	golang.org/x/oauth2 v0.10.0
-	golang.org/x/sys v0.10.0 // indirect
-	golang.org/x/text v0.11.0 // indirect
+	golang.org/x/crypto v0.12.0
+	golang.org/x/net v0.14.0 // indirect
+	golang.org/x/oauth2 v0.11.0
+	golang.org/x/sys v0.11.0 // indirect
+	golang.org/x/text v0.12.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.31.0 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1
 	gopkg.in/yaml.v3 v3.0.1

+ 12 - 12
go.sum

@@ -30,8 +30,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
-github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw=
+github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -112,8 +112,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/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.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
-golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 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=
@@ -123,10 +123,10 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
-golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
-golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
+golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
+golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -141,8 +141,8 @@ golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -150,8 +150,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 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.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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
-golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

+ 1 - 1
k8s/client/netclient-daemonset.yaml

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       hostNetwork: true
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient:v0.20.5
+        image: gravitl/netclient:v0.20.6
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/client/netclient.yaml

@@ -28,7 +28,7 @@ spec:
       #           - "<node label value>"
       #           - "<node label value>"
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient:v0.20.5
+        image: gravitl/netclient:v0.20.6
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/server/netmaker-ui.yaml

@@ -15,7 +15,7 @@ spec:
     spec:
     spec:
       containers:
       containers:
       - name: netmaker-ui
       - name: netmaker-ui
-        image: gravitl/netmaker-ui:v0.20.5
+        image: gravitl/netmaker-ui:v0.20.6
         ports:
         ports:
         - containerPort: 443
         - containerPort: 443
         env:
         env:

+ 15 - 0
logic/clients.go

@@ -1,6 +1,7 @@
 package logic
 package logic
 
 
 import (
 import (
+	"errors"
 	"sort"
 	"sort"
 
 
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
@@ -70,3 +71,17 @@ func SortExtClient(unsortedExtClient []models.ExtClient) {
 		return unsortedExtClient[i].ClientID < unsortedExtClient[j].ClientID
 		return unsortedExtClient[i].ClientID < unsortedExtClient[j].ClientID
 	})
 	})
 }
 }
+
+// GetExtClientByName - gets an ext client by name
+func GetExtClientByName(ID string) (models.ExtClient, error) {
+	clients, err := GetAllExtClients()
+	if err != nil {
+		return models.ExtClient{}, err
+	}
+	for i := range clients {
+		if clients[i].ClientID == ID {
+			return clients[i], nil
+		}
+	}
+	return models.ExtClient{}, errors.New("client not found")
+}

+ 5 - 9
logic/extpeers.go

@@ -152,9 +152,9 @@ func GetExtClientByPubKey(publicKey string, network string) (*models.ExtClient,
 	return nil, fmt.Errorf("no client found")
 	return nil, fmt.Errorf("no client found")
 }
 }
 
 
-// CreateExtClient - creates an extclient
+// CreateExtClient - creates and saves an extclient
 func CreateExtClient(extclient *models.ExtClient) error {
 func CreateExtClient(extclient *models.ExtClient) error {
-	// lock because we need unique IPs and having it concurrent makes parallel calls result in same "unique" IPs
+	// lock because we may need unique IPs and having it concurrent makes parallel calls result in same "unique" IPs
 	addressLock.Lock()
 	addressLock.Lock()
 	defer addressLock.Unlock()
 	defer addressLock.Unlock()
 
 
@@ -219,12 +219,8 @@ func SaveExtClient(extclient *models.ExtClient) error {
 }
 }
 
 
 // UpdateExtClient - updates an ext client with new values
 // UpdateExtClient - updates an ext client with new values
-func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) (*models.ExtClient, error) {
-	new := old
-	err := DeleteExtClient(old.Network, old.ClientID)
-	if err != nil {
-		return new, err
-	}
+func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) models.ExtClient {
+	new := *old
 	new.ClientID = update.ClientID
 	new.ClientID = update.ClientID
 	if update.PublicKey != "" && old.PublicKey != update.PublicKey {
 	if update.PublicKey != "" && old.PublicKey != update.PublicKey {
 		new.PublicKey = update.PublicKey
 		new.PublicKey = update.PublicKey
@@ -241,7 +237,7 @@ func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) (*mo
 	if update.DeniedACLs != nil && !reflect.DeepEqual(old.DeniedACLs, update.DeniedACLs) {
 	if update.DeniedACLs != nil && !reflect.DeepEqual(old.DeniedACLs, update.DeniedACLs) {
 		new.DeniedACLs = update.DeniedACLs
 		new.DeniedACLs = update.DeniedACLs
 	}
 	}
-	return new, CreateExtClient(new)
+	return new
 }
 }
 
 
 // GetExtClientsByID - gets the clients of attached gateway
 // GetExtClientsByID - gets the clients of attached gateway

+ 36 - 12
logic/gateway.go

@@ -11,6 +11,36 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
+// GetAllIngresses - gets all the hosts that are ingresses
+func GetAllIngresses() ([]models.Node, error) {
+	nodes, err := GetAllNodes()
+	if err != nil {
+		return nil, err
+	}
+	ingresses := make([]models.Node, 0)
+	for _, node := range nodes {
+		if node.IsIngressGateway {
+			ingresses = append(ingresses, node)
+		}
+	}
+	return ingresses, nil
+}
+
+// GetAllEgresses - gets all the hosts that are egresses
+func GetAllEgresses() ([]models.Node, error) {
+	nodes, err := GetAllNodes()
+	if err != nil {
+		return nil, err
+	}
+	egresses := make([]models.Node, 0)
+	for _, node := range nodes {
+		if node.IsEgressGateway {
+			egresses = append(egresses, node)
+		}
+	}
+	return egresses, nil
+}
+
 // CreateEgressGateway - creates an egress gateway
 // CreateEgressGateway - creates an egress gateway
 func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
 func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
 	node, err := GetNodeByID(gateway.NodeID)
 	node, err := GetNodeByID(gateway.NodeID)
@@ -28,10 +58,13 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 		return models.Node{}, errors.New("firewall is not supported for egress gateways")
 		return models.Node{}, errors.New("firewall is not supported for egress gateways")
 	}
 	}
 	for i := len(gateway.Ranges) - 1; i >= 0; i-- {
 	for i := len(gateway.Ranges) - 1; i >= 0; i-- {
+		// check if internet gateway IPv4
+		if gateway.Ranges[i] == "0.0.0.0/0" && FreeTier {
+			return models.Node{}, fmt.Errorf("currently IPv4 internet gateways are not supported on the free tier: %s", gateway.Ranges[i])
+		}
+		// check if internet gateway IPv6
 		if gateway.Ranges[i] == "::/0" {
 		if gateway.Ranges[i] == "::/0" {
-			logger.Log(0, "currently IPv6 internet gateways are not supported", gateway.Ranges[i])
-			gateway.Ranges = append(gateway.Ranges[:i], gateway.Ranges[i+1:]...)
-			continue
+			return models.Node{}, fmt.Errorf("currently IPv6 internet gateways are not supported: %s", gateway.Ranges[i])
 		}
 		}
 		normalized, err := NormalizeCIDR(gateway.Ranges[i])
 		normalized, err := NormalizeCIDR(gateway.Ranges[i])
 		if err != nil {
 		if err != nil {
@@ -150,15 +183,6 @@ func DeleteIngressGateway(nodeid string) (models.Node, bool, []models.ExtClient,
 	node.IsIngressGateway = false
 	node.IsIngressGateway = false
 	node.IngressGatewayRange = ""
 	node.IngressGatewayRange = ""
 	node.Failover = false
 	node.Failover = false
-
-	//logger.Log(3, "deleting ingress gateway firewall in use is '", host.FirewallInUse, "' and isEgressGateway is", node.IsEgressGateway)
-	if node.EgressGatewayRequest.NodeID != "" {
-		_, err := CreateEgressGateway(node.EgressGatewayRequest)
-		if err != nil {
-			logger.Log(0, fmt.Sprintf("failed to create egress gateway on node [%s] on network [%s]: %v",
-				node.EgressGatewayRequest.NodeID, node.EgressGatewayRequest.NetID, err))
-		}
-	}
 	err = UpsertNode(&node)
 	err = UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, wasFailover, removedClients, err
 		return models.Node{}, wasFailover, removedClients, err

+ 7 - 8
logic/hosts.go

@@ -158,14 +158,14 @@ func GetHost(hostid string) (*models.Host, error) {
 
 
 // CreateHost - creates a host if not exist
 // CreateHost - creates a host if not exist
 func CreateHost(h *models.Host) error {
 func CreateHost(h *models.Host) error {
-	hosts, err := GetAllHosts()
-	if err != nil && !database.IsEmptyRecord(err) {
-		return err
+	hosts, hErr := GetAllHosts()
+	clients, cErr := GetAllExtClients()
+	if (hErr != nil && !database.IsEmptyRecord(hErr)) ||
+		(cErr != nil && !database.IsEmptyRecord(cErr)) ||
+		len(hosts)+len(clients) >= MachinesLimit {
+		return errors.New("free tier limits exceeded on machines")
 	}
 	}
-	if len(hosts) >= Hosts_Limit {
-		return errors.New("free tier limits exceeded on hosts")
-	}
-	_, err = GetHost(h.ID.String())
+	_, err := GetHost(h.ID.String())
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 		return ErrHostExists
 		return ErrHostExists
 	}
 	}
@@ -198,7 +198,6 @@ func UpdateHost(newHost, currentHost *models.Host) {
 	newHost.Debug = currentHost.Debug
 	newHost.Debug = currentHost.Debug
 	newHost.Nodes = currentHost.Nodes
 	newHost.Nodes = currentHost.Nodes
 	newHost.PublicKey = currentHost.PublicKey
 	newHost.PublicKey = currentHost.PublicKey
-	newHost.InternetGateway = currentHost.InternetGateway
 	newHost.TrafficKeyPublic = currentHost.TrafficKeyPublic
 	newHost.TrafficKeyPublic = currentHost.TrafficKeyPublic
 
 
 	// changeable fields
 	// changeable fields

+ 31 - 0
logic/nodes.go

@@ -1,6 +1,7 @@
 package logic
 package logic
 
 
 import (
 import (
+	"context"
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
@@ -20,6 +21,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/validation"
 	"github.com/gravitl/netmaker/validation"
+	"golang.org/x/exp/slog"
 )
 )
 
 
 var (
 var (
@@ -444,6 +446,35 @@ func GetAllNodesAPI(nodes []models.Node) []models.ApiNode {
 	return apiNodes[:]
 	return apiNodes[:]
 }
 }
 
 
+// DeleteExpiredNodes - goroutine which deletes nodes which are expired
+func DeleteExpiredNodes(ctx context.Context, peerUpdate chan *models.Node) {
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-time.After(time.Hour):
+			// Delete Expired Nodes Every Hour
+			allnodes, err := GetAllNodes()
+			if err != nil {
+				slog.Error("failed to retrieve all nodes", "error", err.Error())
+				return
+			}
+			for _, node := range allnodes {
+				if time.Now().After(node.ExpirationDateTime) {
+					if err := DeleteNode(&node, false); err != nil {
+						slog.Error("error deleting expired node", "nodeid", node.ID.String(), "error", err.Error())
+						continue
+					}
+					node.Action = models.NODE_DELETE
+					node.PendingDelete = true
+					peerUpdate <- &node
+					slog.Info("deleting expired node", "nodeid", node.ID.String())
+				}
+			}
+		}
+	}
+}
+
 // == PRO ==
 // == PRO ==
 
 
 func updateProNodeACLS(node *models.Node) error {
 func updateProNodeACLS(node *models.Node) error {

+ 19 - 16
logic/serverconf.go

@@ -2,22 +2,23 @@ package logic
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
 var (
 var (
-	// Networks_Limit - dummy var for community
-	Networks_Limit = 1000000000
-	// Users_Limit - dummy var for community
-	Users_Limit = 1000000000
-	// Clients_Limit - dummy var for community
-	Clients_Limit = 1000000000
-	// Hosts_Limit - dummy var for community
-	Hosts_Limit = 1000000000
-	// Free_Tier - specifies if free tier
-	Free_Tier = false
+	// NetworksLimit - dummy var for community
+	NetworksLimit = 1000000000
+	// UsersLimit - dummy var for community
+	UsersLimit = 1000000000
+	// MachinesLimit - dummy var for community
+	MachinesLimit = 1000000000
+	// IngressesLimit - dummy var for community
+	IngressesLimit = 1000000000
+	// EgressesLimit - dummy var for community
+	EgressesLimit = 1000000000
+	// FreeTier - specifies if free tier
+	FreeTier = false
 )
 )
 
 
 type serverData struct {
 type serverData struct {
@@ -87,10 +88,12 @@ func StoreJWTSecret(privateKey string) error {
 	return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME)
 	return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME)
 }
 }
 
 
+// SetFreeTierLimits - sets limits for free tier
 func SetFreeTierLimits() {
 func SetFreeTierLimits() {
-	Free_Tier = true
-	Users_Limit = servercfg.GetUserLimit()
-	Clients_Limit = servercfg.GetClientLimit()
-	Networks_Limit = servercfg.GetNetworkLimit()
-	Hosts_Limit = servercfg.GetHostLimit()
+	FreeTier = true
+	UsersLimit = servercfg.GetUserLimit()
+	NetworksLimit = servercfg.GetNetworkLimit()
+	MachinesLimit = servercfg.GetMachinesLimit()
+	IngressesLimit = servercfg.GetIngressLimit()
+	EgressesLimit = servercfg.GetEgressLimit()
 }
 }

+ 8 - 4
logic/timer.go

@@ -3,10 +3,11 @@ package logic
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
+	"github.com/gravitl/netmaker/logger"
+	"golang.org/x/exp/slog"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 )
 )
 
 
@@ -52,7 +53,7 @@ func StartHookManager(ctx context.Context, wg *sync.WaitGroup) {
 	for {
 	for {
 		select {
 		select {
 		case <-ctx.Done():
 		case <-ctx.Done():
-			logger.Log(0, "## Stopping Hook Manager")
+			slog.Error("## Stopping Hook Manager")
 			return
 			return
 		case newhook := <-HookManagerCh:
 		case newhook := <-HookManagerCh:
 			wg.Add(1)
 			wg.Add(1)
@@ -70,7 +71,9 @@ func addHookWithInterval(ctx context.Context, wg *sync.WaitGroup, hook func() er
 		case <-ctx.Done():
 		case <-ctx.Done():
 			return
 			return
 		case <-ticker.C:
 		case <-ticker.C:
-			hook()
+			if err := hook(); err != nil {
+				slog.Error(err.Error())
+			}
 		}
 		}
 	}
 	}
 
 
@@ -85,6 +88,7 @@ var timeHooks = []interface{}{
 }
 }
 
 
 func loggerDump() error {
 func loggerDump() error {
+	// TODO use slog?
 	logger.DumpFile(fmt.Sprintf("data/netmaker.log.%s", time.Now().Format(logger.TimeFormatDay)))
 	logger.DumpFile(fmt.Sprintf("data/netmaker.log.%s", time.Now().Format(logger.TimeFormatDay)))
 	return nil
 	return nil
 }
 }
@@ -93,7 +97,7 @@ func loggerDump() error {
 func runHooks() {
 func runHooks() {
 	for _, hook := range timeHooks {
 	for _, hook := range timeHooks {
 		if err := hook.(func() error)(); err != nil {
 		if err := hook.(func() error)(); err != nil {
-			logger.Log(1, "error occurred when running timer function:", err.Error())
+			slog.Error("error occurred when running timer function", "error", err.Error())
 		}
 		}
 	}
 	}
 }
 }

+ 2 - 2
logic/zombie.go

@@ -37,7 +37,7 @@ func CheckZombies(newnode *models.Node) {
 			//skip self
 			//skip self
 			continue
 			continue
 		}
 		}
-		if node.HostID == newnode.HostID || time.Now().After(node.ExpirationDateTime) {
+		if node.HostID == newnode.HostID {
 			logger.Log(0, "adding ", node.ID.String(), " to zombie list")
 			logger.Log(0, "adding ", node.ID.String(), " to zombie list")
 			newZombie <- node.ID
 			newZombie <- node.ID
 		}
 		}
@@ -97,7 +97,7 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 						zombies = append(zombies[:i], zombies[i+1:]...)
 						zombies = append(zombies[:i], zombies[i+1:]...)
 						continue
 						continue
 					}
 					}
-					if time.Since(node.LastCheckIn) > time.Minute*ZOMBIE_DELETE_TIME || time.Now().After(node.ExpirationDateTime) {
+					if time.Since(node.LastCheckIn) > time.Minute*ZOMBIE_DELETE_TIME {
 						if err := DeleteNode(&node, true); err != nil {
 						if err := DeleteNode(&node, true); err != nil {
 							logger.Log(1, "error deleting zombie node", zombies[i].String(), err.Error())
 							logger.Log(1, "error deleting zombie node", zombies[i].String(), err.Error())
 							continue
 							continue

+ 2 - 1
main.go

@@ -29,7 +29,7 @@ import (
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 )
 )
 
 
-var version = "v0.20.5"
+var version = "v0.20.6"
 
 
 // Start DB Connection and start API Request Handler
 // Start DB Connection and start API Request Handler
 func main() {
 func main() {
@@ -168,6 +168,7 @@ func runMessageQueue(wg *sync.WaitGroup, ctx context.Context) {
 	go func() {
 	go func() {
 		peerUpdate := make(chan *models.Node)
 		peerUpdate := make(chan *models.Node)
 		go logic.ManageZombies(ctx, peerUpdate)
 		go logic.ManageZombies(ctx, peerUpdate)
+		go logic.DeleteExpiredNodes(ctx, peerUpdate)
 		for nodeUpdate := range peerUpdate {
 		for nodeUpdate := range peerUpdate {
 			if err := mq.NodeUpdate(nodeUpdate); err != nil {
 			if err := mq.NodeUpdate(nodeUpdate); err != nil {
 				logger.Log(0, "failed to send peer update for deleted node: ", nodeUpdate.ID.String(), err.Error())
 				logger.Log(0, "failed to send peer update for deleted node: ", nodeUpdate.ID.String(), err.Error())

+ 0 - 6
models/api_host.go

@@ -23,7 +23,6 @@ type ApiHost struct {
 	EndpointIP         string   `json:"endpointip" yaml:"endpointip"`
 	EndpointIP         string   `json:"endpointip" yaml:"endpointip"`
 	PublicKey          string   `json:"publickey"`
 	PublicKey          string   `json:"publickey"`
 	MacAddress         string   `json:"macaddress"`
 	MacAddress         string   `json:"macaddress"`
-	InternetGateway    string   `json:"internetgateway"`
 	Nodes              []string `json:"nodes"`
 	Nodes              []string `json:"nodes"`
 	IsDefault          bool     `json:"isdefault" yaml:"isdefault"`
 	IsDefault          bool     `json:"isdefault" yaml:"isdefault"`
 	IsRelayed          bool     `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
 	IsRelayed          bool     `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
@@ -45,10 +44,6 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 		a.Interfaces[i].AddressString = a.Interfaces[i].Address.String()
 		a.Interfaces[i].AddressString = a.Interfaces[i].Address.String()
 	}
 	}
 	a.DefaultInterface = h.DefaultInterface
 	a.DefaultInterface = h.DefaultInterface
-	a.InternetGateway = h.InternetGateway.String()
-	if isEmptyAddr(a.InternetGateway) {
-		a.InternetGateway = ""
-	}
 	a.IsStatic = h.IsStatic
 	a.IsStatic = h.IsStatic
 	a.ListenPort = h.ListenPort
 	a.ListenPort = h.ListenPort
 	a.MTU = h.MTU
 	a.MTU = h.MTU
@@ -83,7 +78,6 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.Interface = currentHost.Interface
 	h.Interface = currentHost.Interface
 	h.Interfaces = currentHost.Interfaces
 	h.Interfaces = currentHost.Interfaces
 	h.DefaultInterface = currentHost.DefaultInterface
 	h.DefaultInterface = currentHost.DefaultInterface
-	h.InternetGateway = currentHost.InternetGateway
 	h.IsDocker = currentHost.IsDocker
 	h.IsDocker = currentHost.IsDocker
 	h.IsK8S = currentHost.IsK8S
 	h.IsK8S = currentHost.IsK8S
 	h.IsStatic = a.IsStatic
 	h.IsStatic = a.IsStatic

+ 2 - 1
models/host.go

@@ -55,7 +55,6 @@ type Host struct {
 	PublicKey          wgtypes.Key      `json:"publickey" yaml:"publickey"`
 	PublicKey          wgtypes.Key      `json:"publickey" yaml:"publickey"`
 	MacAddress         net.HardwareAddr `json:"macaddress" yaml:"macaddress"`
 	MacAddress         net.HardwareAddr `json:"macaddress" yaml:"macaddress"`
 	TrafficKeyPublic   []byte           `json:"traffickeypublic" yaml:"traffickeypublic"`
 	TrafficKeyPublic   []byte           `json:"traffickeypublic" yaml:"traffickeypublic"`
-	InternetGateway    net.UDPAddr      `json:"internetgateway" yaml:"internetgateway"`
 	Nodes              []string         `json:"nodes" yaml:"nodes"`
 	Nodes              []string         `json:"nodes" yaml:"nodes"`
 	Interfaces         []Iface          `json:"interfaces" yaml:"interfaces"`
 	Interfaces         []Iface          `json:"interfaces" yaml:"interfaces"`
 	DefaultInterface   string           `json:"defaultinterface" yaml:"defaultinterface"`
 	DefaultInterface   string           `json:"defaultinterface" yaml:"defaultinterface"`
@@ -108,6 +107,8 @@ const (
 	RegisterWithTurn = "REGISTER_WITH_TURN"
 	RegisterWithTurn = "REGISTER_WITH_TURN"
 	// UpdateKeys - update wireguard private/public keys
 	// UpdateKeys - update wireguard private/public keys
 	UpdateKeys = "UPDATE_KEYS"
 	UpdateKeys = "UPDATE_KEYS"
+	// RequestPull - request a pull from a host
+	RequestPull = "REQ_PULL"
 )
 )
 
 
 // SignalAction - turn peer signal action
 // SignalAction - turn peer signal action

+ 3 - 1
models/migrate.go

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

+ 4 - 4
models/network.go

@@ -11,10 +11,10 @@ import (
 type Network struct {
 type Network struct {
 	AddressRange        string                `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"`
 	AddressRange        string                `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"`
 	AddressRange6       string                `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"`
 	AddressRange6       string                `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"`
-	NetID               string                `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
+	NetID               string                `json:"netid" bson:"netid" validate:"required,min=1,max=32,netid_valid"`
 	NodesLastModified   int64                 `json:"nodeslastmodified" bson:"nodeslastmodified"`
 	NodesLastModified   int64                 `json:"nodeslastmodified" bson:"nodeslastmodified"`
 	NetworkLastModified int64                 `json:"networklastmodified" bson:"networklastmodified"`
 	NetworkLastModified int64                 `json:"networklastmodified" bson:"networklastmodified"`
-	DefaultInterface    string                `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=15"`
+	DefaultInterface    string                `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=35"`
 	DefaultListenPort   int32                 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"`
 	DefaultListenPort   int32                 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"`
 	NodeLimit           int32                 `json:"nodelimit" bson:"nodelimit"`
 	NodeLimit           int32                 `json:"nodelimit" bson:"nodelimit"`
 	DefaultPostDown     string                `json:"defaultpostdown" bson:"defaultpostdown"`
 	DefaultPostDown     string                `json:"defaultpostdown" bson:"defaultpostdown"`
@@ -30,7 +30,7 @@ type Network struct {
 
 
 // SaveData - sensitive fields of a network that should be kept the same
 // SaveData - sensitive fields of a network that should be kept the same
 type SaveData struct { // put sensitive fields here
 type SaveData struct { // put sensitive fields here
-	NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
+	NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=32,netid_valid"`
 }
 }
 
 
 // Network.SetNodesLastModified - sets nodes last modified on network, depricated
 // Network.SetNodesLastModified - sets nodes last modified on network, depricated
@@ -49,7 +49,7 @@ func (network *Network) SetDefaults() {
 		network.DefaultUDPHolePunch = "no"
 		network.DefaultUDPHolePunch = "no"
 	}
 	}
 	if network.DefaultInterface == "" {
 	if network.DefaultInterface == "" {
-		if len(network.NetID) < 13 {
+		if len(network.NetID) < 33 {
 			network.DefaultInterface = "nm-" + network.NetID
 			network.DefaultInterface = "nm-" + network.NetID
 		} else {
 		} else {
 			network.DefaultInterface = network.NetID
 			network.DefaultInterface = network.NetID

+ 4 - 11
models/node.go

@@ -100,7 +100,6 @@ type Node struct {
 // LegacyNode - legacy struct for node model
 // LegacyNode - legacy struct for node model
 type LegacyNode struct {
 type LegacyNode struct {
 	ID                      string               `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5,id_unique"`
 	ID                      string               `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5,id_unique"`
-	HostID                  string               `json:"hostid,omitempty" bson:"id,omitempty" yaml:"hostid,omitempty" validate:"required,min=5,id_unique"`
 	Address                 string               `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
 	Address                 string               `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
 	Address6                string               `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
 	Address6                string               `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
 	LocalAddress            string               `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty"`
 	LocalAddress            string               `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty"`
@@ -109,7 +108,6 @@ type LegacyNode struct {
 	NetworkSettings         Network              `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
 	NetworkSettings         Network              `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
 	ListenPort              int32                `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
 	ListenPort              int32                `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
 	LocalListenPort         int32                `json:"locallistenport" bson:"locallistenport" yaml:"locallistenport" validate:"numeric,min=0,max=65535"`
 	LocalListenPort         int32                `json:"locallistenport" bson:"locallistenport" yaml:"locallistenport" validate:"numeric,min=0,max=65535"`
-	ProxyListenPort         int32                `json:"proxy_listen_port" bson:"proxy_listen_port" yaml:"proxy_listen_port" validate:"numeric,min=0,max=65535"`
 	PublicKey               string               `json:"publickey" bson:"publickey" yaml:"publickey" validate:"required,base64"`
 	PublicKey               string               `json:"publickey" bson:"publickey" yaml:"publickey" validate:"required,base64"`
 	Endpoint                string               `json:"endpoint" bson:"endpoint" yaml:"endpoint" validate:"required,ip"`
 	Endpoint                string               `json:"endpoint" bson:"endpoint" yaml:"endpoint" validate:"required,ip"`
 	AllowedIPs              []string             `json:"allowedips" bson:"allowedips" yaml:"allowedips"`
 	AllowedIPs              []string             `json:"allowedips" bson:"allowedips" yaml:"allowedips"`
@@ -153,8 +151,6 @@ type LegacyNode struct {
 	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"`
 	Connected       string      `json:"connected" bson:"connected" yaml:"connected" validate:"checkyesorno"`
-	PendingDelete   bool        `json:"pendingdelete" bson:"pendingdelete" yaml:"pendingdelete"`
-	Proxy           bool        `json:"proxy" bson:"proxy" yaml:"proxy"`
 	// == PRO ==
 	// == PRO ==
 	DefaultACL string `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
 	DefaultACL string `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
 	OwnerID    string `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`
 	OwnerID    string `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`
@@ -341,7 +337,9 @@ func (node *Node) SetLastPeerUpdate() {
 
 
 // Node.SetExpirationDateTime - sets node expiry time
 // Node.SetExpirationDateTime - sets node expiry time
 func (node *Node) SetExpirationDateTime() {
 func (node *Node) SetExpirationDateTime() {
-	node.ExpirationDateTime = time.Now().Add(TEN_YEARS_IN_SECONDS)
+	if node.ExpirationDateTime.IsZero() {
+		node.ExpirationDateTime = time.Now().Add(TEN_YEARS_IN_SECONDS)
+	}
 }
 }
 
 
 // Node.SetDefaultName - sets a random name to node
 // Node.SetDefaultName - sets a random name to node
@@ -484,10 +482,6 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) {
 		host.PublicKey, _ = wgtypes.ParseKey(ln.PublicKey)
 		host.PublicKey, _ = wgtypes.ParseKey(ln.PublicKey)
 		host.MacAddress, _ = net.ParseMAC(ln.MacAddress)
 		host.MacAddress, _ = net.ParseMAC(ln.MacAddress)
 		host.TrafficKeyPublic = ln.TrafficKeys.Mine
 		host.TrafficKeyPublic = ln.TrafficKeys.Mine
-		gateway, err := net.ResolveUDPAddr("udp", ln.InternetGateway)
-		if err == nil {
-			host.InternetGateway = *gateway
-		}
 		id, _ := uuid.Parse(ln.ID)
 		id, _ := uuid.Parse(ln.ID)
 		host.Nodes = append(host.Nodes, id.String())
 		host.Nodes = append(host.Nodes, id.String())
 		host.Interfaces = ln.Interfaces
 		host.Interfaces = ln.Interfaces
@@ -529,7 +523,7 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) {
 func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode {
 func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode {
 	l := LegacyNode{}
 	l := LegacyNode{}
 	l.ID = n.ID.String()
 	l.ID = n.ID.String()
-	l.HostID = h.ID.String()
+	//l.HostID = h.ID.String()
 	l.Address = n.Address.String()
 	l.Address = n.Address.String()
 	l.Address6 = n.Address6.String()
 	l.Address6 = n.Address6.String()
 	l.Interfaces = h.Interfaces
 	l.Interfaces = h.Interfaces
@@ -572,7 +566,6 @@ func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode {
 	l.TrafficKeys.Mine = h.TrafficKeyPublic
 	l.TrafficKeys.Mine = h.TrafficKeyPublic
 	l.TrafficKeys.Server = s.TrafficKey
 	l.TrafficKeys.Server = s.TrafficKey
 	l.FirewallInUse = h.FirewallInUse
 	l.FirewallInUse = h.FirewallInUse
-	l.InternetGateway = h.InternetGateway.String()
 	l.Connected = formatBool(n.Connected)
 	l.Connected = formatBool(n.Connected)
 	//l.PendingDelete = formatBool(n.PendingDelete)
 	//l.PendingDelete = formatBool(n.PendingDelete)
 	l.DefaultACL = n.DefaultACL
 	l.DefaultACL = n.DefaultACL

+ 2 - 2
mq/publishers.go

@@ -176,7 +176,7 @@ func PublishDNSUpdate(network string, dns models.DNSUpdate) error {
 	for _, node := range nodes {
 	for _, node := range nodes {
 		host, err := logic.GetHost(node.HostID.String())
 		host, err := logic.GetHost(node.HostID.String())
 		if err != nil {
 		if err != nil {
-			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			logger.Log(0, "error retrieving host for dns update", node.HostID.String(), err.Error())
 			continue
 			continue
 		}
 		}
 		data, err := json.Marshal(dns)
 		data, err := json.Marshal(dns)
@@ -370,7 +370,7 @@ func getNodeDNS(network string) []models.DNSUpdate {
 	for _, node := range nodes {
 	for _, node := range nodes {
 		host, err := logic.GetHost(node.HostID.String())
 		host, err := logic.GetHost(node.HostID.String())
 		if err != nil {
 		if err != nil {
-			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			logger.Log(0, "error retrieving host for dns update", node.HostID.String(), err.Error())
 			continue
 			continue
 		}
 		}
 		dns.Action = models.DNSInsert
 		dns.Action = models.DNSInsert

+ 7 - 10
release.md

@@ -1,22 +1,19 @@
 
 
-# Netmaker v0.20.5
+# Netmaker v0.20.6
 
 
 ## Whats New
 ## Whats New
-- Extclient Acls
-- Force delete host along with all the associated nodes
+- Sync clients with server state from UI
 
 
 ## What's Fixed
 ## What's Fixed
-- Deprecated Proxy
-- Solved Race condition for multiple nodes joining network at same time
-- Node dns toggle
-- Simplified Firewall rules for added stability
+- Upgrade Process from v0.17.1 to latest version can be now done seamlessly, please refer docs for more information
+- Expired nodes clean up is handled correctly now
+- Ext client config generation fixed for ipv6 endpoints
+- installation process will only generate certs required for required Domains based on CE or EE
+- support for ARM machines on install script
      
      
 ## known issues
 ## known issues
-- Expired nodes are not getting cleaned up
 - Windows installer does not install WireGuard
 - Windows installer does not install WireGuard
 - netclient-gui will continously display error dialog if netmaker server is offline
 - netclient-gui will continously display error dialog if netmaker server is offline
-- Incorrect metrics against ext clients
-- Host ListenPorts set to 0 after migration from 0.17.1 -> 0.20.5
 - Mac IPv6 addresses/route issues
 - Mac IPv6 addresses/route issues
 - Docker client can not re-join after complete deletion
 - Docker client can not re-join after complete deletion
 - netclient-gui network tab blank after disconnect
 - netclient-gui network tab blank after disconnect

+ 0 - 78
scripts/netclient-install.ps1

@@ -1,78 +0,0 @@
-new-module -name netclient-install -scriptblock {
-    $ErrorActionPreference = "Stop"
-
-    function Quit {
-        param(
-            $Text
-        )
-        Write-Host "Exiting: " $Text
-        Break Script
-    }
-    Function Netclient-Install() {
-        param ($version='latest', $token)
-
-            if($token -eq $null -or $token -eq ""){
-                Quit "-token required"
-            }
-
-            $software = "WireGuard";
-            $installed = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -eq $software }) -ne $null
-
-            If(-Not $installed) {
-                Write-Host "'$software' is NOT installed. installing...";
-                $url = "https://download.wireguard.com/windows-client/wireguard-installer.exe"
-                $outpath = "$env:userprofile\Downloads\wireguard-installer.exe"
-                Invoke-WebRequest -Uri $url -OutFile $outpath
-                $args = @("Comma","Separated","Arguments")
-                Start-Process -Filepath "$env:userprofile\Downloads\wireguard-installer.exe" -ArgumentList $args -Wait
-                $software = "WireGuard";
-                $installed = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -eq $software }) -ne $null
-                If(-Not $installed) {
-                    Quit "Could not install WireGuard"
-                } else {
-                    # $env:Path +=  (";" + $env:ProgramFiles + "\WireGuard")
-                    Write-Host "'$software' is installed."
-                }
-            } else {
-                Write-Host "'$software' is installed."
-            }
-            $outpath = "";
-            if (Test-Path -Path "C:\ProgramData\Netclient\bin\netclient.exe") {
-                $outpath = "C:\ProgramData\Netclient\bin\netclient.exe";
-            } else {
-                $outpath = "$env:userprofile\Downloads\netclient.exe"
-                Write-Host "'netclient.exe' is NOT installed. installing...";
-                Write-Host "https://github.com/gravitl/netmaker/releases/download/$version/netclient.exe";
-                $url = "https://github.com/gravitl/netmaker/releases/download/$version/netclient.exe"
-                Invoke-WebRequest -Uri $url -OutFile $outpath
-                $loc = Get-Location
-                Copy-Item -Path "$env:userprofile\Downloads\netclient.exe" -Destination "$loc\netclient.exe"
-            }
-            $runNum = "one"
-            foreach ($run in $runNum) { 
-
-                $NetArgs = @("join","-t",$token)
-                Start-Process -Filepath $outpath -ArgumentList $NetArgs -Wait
-                Add-MpPreference -ExclusionPath "C:\ProgramData\Netclient"
-
-                if ((Get-Command "netclient.exe" -ErrorAction SilentlyContinue) -eq $null) { 
-                    if (-not (Test-Path -Path "C:\ProgramData\Netclient\bin\netclient.exe")) {
-                        New-Item -Path "C:\ProgramData\Netclient" -Name "bin" -ItemType "directory"
-                        Move-Item -Path "$env:userprofile\Downloads\netclient.exe" -Destination "C:\ProgramData\Netclient\bin\netclient.exe"
-                        $oldpath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).path
-                        $newpath = "$oldpath;C:\ProgramData\Netclient\bin"
-                        Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newPath
-                        $env:Path += ";C:\ProgramData\Netclient\bin"
-                    }
-                }
-                #if($run -eq "one"){
-                #    Write-Host "re-running setup to confirm all components are installed."
-                #    Start-Sleep -s 1
-                #}
-                
-            }
-        Start-Sleep -s 5
-        Write-Host "'netclient' is installed."
-    }
-}
-

+ 0 - 307
scripts/netclient-install.sh

@@ -1,307 +0,0 @@
-#!/bin/sh
-
-if [ $(id -u) -ne 0 ]; then
-   echo "This script must be run as root"
-   exit 1
-fi
-
-echo "checking dependencies..."
-
-OS=$(uname)
-
-if [ -f /etc/debian_version ]; then
-	dependencies="wireguard wireguard-tools"
-	update_cmd='apt update'
-	install_cmd='apt-get install -y'
-elif [ -f /etc/alpine-release ]; then
-	dependencies="wireguard"
-	update_cmd='apk update'
-	install_cmd='apk --update add'
-elif [ -f /etc/centos-release ]; then
-	dependencies="wireguard"
-	update_cmd='yum update'
-	install_cmd='yum install -y'
-elif [ -f /etc/fedora-release ]; then
-	dependencies="wireguard"
-	update_cmd='dnf update'
-	install_cmd='dnf install -y'
-elif [ -f /etc/redhat-release ]; then
-	dependencies="wireguard"
-	update_cmd='yum update'
-	install_cmd='yum install -y'
-elif [ -f /etc/arch-release ]; then
-    	dependecies="wireguard-tools"
-	update_cmd='pacman -Sy'
-	install_cmd='pacman -S --noconfirm'
-elif [ "${OS}" = "FreeBSD" ]; then
-	dependencies="wireguard wget"
-	update_cmd='pkg update'
-	install_cmd='pkg install -y'
-elif [ -f /etc/turris-version ]; then
-	dependencies="wireguard-tools bash"
-	OS="TurrisOS"
-	update_cmd='opkg update'	
-	install_cmd='opkg install'
-elif [ -f /etc/openwrt_release ]; then
-	dependencies="wireguard-tools bash"
-	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
-
-${update_cmd}
-
-set -- $dependencies
-while [ -n "$1" ]; do
-    echo $1
-	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
-			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)
-		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
-				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
-
-set -e
-
-[ -z "$KEY" ] && KEY=nokey;
-[ -z "$VERSION" ] && echo "no \$VERSION provided, fallback to latest" && VERSION=latest;
-[ "latest" != "$VERSION" ] && [ "v" != `echo $VERSION | cut -c1` ] && VERSION="v$VERSION"
-[ -z "$NAME" ] && NAME="";
-
-dist=netclient
-
-echo "OS Version = $(uname)"
-echo "Netclient Version = $VERSION"
-
-case $(uname | tr A-Z a-z) in
-	linux*)
-		if [ -z "$CPU_ARCH" ]; then
-			CPU_ARCH=$(uname -m)
-		fi
-		case $CPU_ARCH in
-			amd64)
-				dist=netclient
-			;;
-			x86_64)
-				dist=netclient
-			;;
- 			arm64)
-				dist=netclient-arm64
-			;;
-			aarch64)
-                dist=netclient-arm64
-			;;
-			armv6l)
-                dist=netclient-arm6
-			;;
-			armv7l)
-                dist=netclient-arm7
-			;;
-			arm*)
-				dist=netclient-$CPU_ARCH
-			;;
-			mipsle)
-                dist=netclient-mipsle
-			;;
-			mips)
-			    #If binary in the below condition is not compatible with your hardware, retry with other netclient-mips* binaries.
-				if [[ `printf '\0\1' | hexdump -e '/2 "%04x"'` -eq 0100 ]]; then
-					#Little Endian, tested and confirmed in GL-MT1300 OS "OpenWrt 19.07.8"
-					dist=netclient-mipsle-softfloat
-				else
-					#Big Endian, tested and confirmed in DSL-2750U OS "OpenWrt 22.03.2"
-					dist=netclient-mips-softfloat
-				fi
-			;;
-			*)
-				fatal "$CPU_ARCH : cpu architecture not supported"
-    		esac
-	;;
-	darwin)
-        dist=netclient-darwin
-	;;
-	Darwin)
-        dist=netclient-darwin
-	;;
-	freebsd*)
-		if [ -z "$CPU_ARCH" ]; then
-			CPU_ARCH=$(uname -m)
-		fi
-		case $CPU_ARCH in
-			amd64)
-				dist=netclient-freebsd
-			;;
-			x86_64)
-				dist=netclient-freebsd
-			;;
- 			arm64)
-				dist=netclient-freebsd-arm64
-			;;
-			aarch64)
-                dist=netclient-freebsd-arm64
-			;;
-			armv7l)
-                dist=netclient-freebsd-arm7
-			;;
-			arm*)
-				dist=netclient-freebsd-$CPU_ARCH
-            ;;
-			*)
-				fatal "$CPU_ARCH : cpu architecture not supported"
-    		esac
-	;;
-esac
-
-echo "Binary = $dist"
-
-url="https://github.com/gravitl/netmaker/releases/download/$VERSION/$dist"
-curl_opts='-nv'
-if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
-	curl_opts='-q'
-fi
-
-if curl --output /dev/null --silent --head --fail "$url"; then
-	echo "Downloading $dist $VERSION"
-	wget $curl_opts -O netclient $url
-else
-	echo "Downloading $dist latest"
-	wget $curl_opts -O netclient https://github.com/gravitl/netmaker/releases/latest/download/$dist
-fi
-
-chmod +x netclient
-
-EXTRA_ARGS=""
-if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
-	EXTRA_ARGS="--daemon=off"
-fi
-
-if [ "${KEY}" != "nokey" ]; then
-  if [ -z "${NAME}" ]; then
-    ./netclient join -t $KEY $EXTRA_ARGS
-  else
-    ./netclient join -t $KEY --name $NAME $EXTRA_ARGS
-  fi
-fi
-
-if [ "${OS}" = "FreeBSD" ]; then
-  if ! [ -x /usr/sbin/netclient ]; then
-    echo "Moving netclient executable to \"/usr/sbin/netclient\""
-    mv netclient /usr/sbin  
-  else
-    echo "Netclient already present."
-  fi
-fi
-
-if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
-	mv ./netclient /sbin/netclient
-
-	if [ "${OS}" = "TurrisOS" ]; then
-		url="https://raw.githubusercontent.com/gravitl/netmaker/$VERSION/scripts/openwrt-daemon.sh"
-		if curl --output /dev/null --silent --head --fail $url; then
-			wget $curl_opts -O netclient.service.tmp $url
-		else
-			wget $curl_opts -O netclient.service.tmp https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/openwrt-daemon.sh
-		fi
-	elif [ "${OS}" = "OpenWRT" ] && [ "$CPU_ARCH" = "mips" ]; then
-		wget $curl_opts -O netclient.service.tmp https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/openwrt-daemon.sh
-	else
-		cat << 'END_OF_FILE' > ./netclient.service.tmp
-#!/bin/sh /etc/rc.common
-
-EXTRA_COMMANDS="status"
-EXTRA_HELP="        status      Check service is running"
-START=99
-
-LOG_FILE="/tmp/netclient.logs"
-
-start() {
-  if [ ! -f "${LOG_FILE}" ];then
-      touch "${LOG_FILE}"
-  fi
-  local PID=$(ps|grep "netclient daemon"|grep -v grep|awk '{print $1}')
-  if [ "${PID}" ];then
-    echo "service is running"
-    return
-  fi
-  bash -c "do /sbin/netclient daemon  >> ${LOG_FILE} 2>&1;\
-           if [ $(ls -l ${LOG_FILE}|awk '{print $5}') -gt 10240000 ];then tar zcf "${LOG_FILE}.tar" -C / "tmp/netclient.logs"  && > $LOG_FILE;fi;done &"
-  echo "start"
-}
-
-stop() {
-  pids=$(ps|grep "netclient daemon"|grep -v grep|awk '{print $1}')
-  for i in "${pids[@]}"
-  do
-	if [ "${i}" ];then
-		kill "${i}"
-	fi
-  done
-  echo "stop"
-}
-
-status() {
-  local PID=$(ps|grep "netclient daemon"|grep -v grep|awk '{print $1}')
-  if [ "${PID}" ];then
-    echo -e "netclient[${PID}] is running \n"
-  else
-    echo -e "netclient is not running \n"
-  fi
-}
-
-END_OF_FILE
-	fi
-	mv ./netclient.service.tmp /etc/init.d/netclient
-	chmod +x /etc/init.d/netclient
-	/etc/init.d/netclient enable
-	/etc/init.d/netclient start
-else 
-	rm -f netclient
-fi

+ 1 - 1
scripts/netmaker.default.env

@@ -41,7 +41,7 @@ DISPLAY_KEYS="on"
 DATABASE="sqlite"
 DATABASE="sqlite"
 # The address of the mq server. If running from docker compose it will be "mq". Otherwise, need to input address.
 # The address of the mq server. If running from docker compose it will be "mq". Otherwise, need to input address.
 # If using "host networking", it will find and detect the IP of the mq container.
 # If using "host networking", it will find and detect the IP of the mq container.
-SERVER_BROKER_ENDPOINT="ws://mq:1883"
+SERVER_BROKER_ENDPOINT="ws://mq:1883" # For EMQX websockets use `SERVER_BROKER_ENDPOINT=ws://mq:8083/mqtt`
 # The reachable port of STUN on the server
 # The reachable port of STUN on the server
 STUN_PORT="3478"
 STUN_PORT="3478"
 # Logging verbosity level - 1, 2, or 3
 # Logging verbosity level - 1, 2, or 3

+ 26 - 12
scripts/nm-certs.sh

@@ -27,20 +27,34 @@ if [ -n "$(docker ps | grep caddy)" ]; then
 	docker-compose -f /root/docker-compose.yml stop caddy
 	docker-compose -f /root/docker-compose.yml stop caddy
 fi
 fi
 
 
-CERTBOT_PARAMS=$(cat <<EOF
-certonly --standalone \
-	--non-interactive --agree-tos \
-	-m $NM_EMAIL \
-	-d api.$NM_DOMAIN \
-	-d broker.$NM_DOMAIN \
-	-d dashboard.$NM_DOMAIN \
-	-d turn.$NM_DOMAIN \
-	-d turnapi.$NM_DOMAIN \
-	-d netmaker-exporter.$NM_DOMAIN \
-	-d grafana.$NM_DOMAIN \
-	-d prometheus.$NM_DOMAIN
+if [ "$INSTALL_TYPE" = "ce" ]; then
+	CERTBOT_PARAMS=$(cat <<EOF
+	certonly --standalone \
+		--non-interactive --agree-tos \
+		-m $NM_EMAIL \
+		-d api.$NM_DOMAIN \
+		-d broker.$NM_DOMAIN \
+		-d dashboard.$NM_DOMAIN \
+		-d turn.$NM_DOMAIN \
+		-d turnapi.$NM_DOMAIN
 EOF
 EOF
 )
 )
+elif [ "$INSTALL_TYPE" = "ee" ]; then
+	CERTBOT_PARAMS=$(cat <<EOF
+	certonly --standalone \
+		--non-interactive --expand --agree-tos \
+		-m $NM_EMAIL \
+		-d api.$NM_DOMAIN \
+		-d broker.$NM_DOMAIN \
+		-d dashboard.$NM_DOMAIN \
+		-d turn.$NM_DOMAIN \
+		-d turnapi.$NM_DOMAIN \
+		-d netmaker-exporter.$NM_DOMAIN \
+		-d grafana.$NM_DOMAIN \
+		-d prometheus.$NM_DOMAIN
+EOF
+)
+fi
 
 
 # generate an entrypoint for zerossl-certbot
 # generate an entrypoint for zerossl-certbot
 cat <<EOF >"$SCRIPT_DIR/certbot-entry.sh"
 cat <<EOF >"$SCRIPT_DIR/certbot-entry.sh"

+ 12 - 5
scripts/nm-quick.sh

@@ -176,8 +176,7 @@ setup_netclient() {
 	netclient uninstall
 	netclient uninstall
 	set -e
 	set -e
 
 
-	# TODO arm support
-	wget -qO netclient https://github.com/gravitl/netclient/releases/download/$LATEST/netclient-linux-amd64
+	wget -qO netclient https://github.com/gravitl/netclient/releases/download/$LATEST/netclient-linux-$ARCH
 	chmod +x netclient
 	chmod +x netclient
 	./netclient install
 	./netclient install
 	echo "Register token: $TOKEN"
 	echo "Register token: $TOKEN"
@@ -227,8 +226,7 @@ configure_netclient() {
 # setup_nmctl - pulls nmctl and makes it executable
 # setup_nmctl - pulls nmctl and makes it executable
 setup_nmctl() {
 setup_nmctl() {
 
 
-	# TODO arm support
-	local URL="https://github.com/gravitl/netmaker/releases/download/$LATEST/nmctl-linux-amd64"
+	local URL="https://github.com/gravitl/netmaker/releases/download/$LATEST/nmctl-linux-$ARCH"
 	echo "Downloading nmctl..."
 	echo "Downloading nmctl..."
 	wget -qO /usr/bin/nmctl "$URL"
 	wget -qO /usr/bin/nmctl "$URL"
 
 
@@ -430,7 +428,16 @@ install_dependencies() {
 		#  ask the user to install manually and continue when ready
 		#  ask the user to install manually and continue when ready
 		exit 1
 		exit 1
 	fi
 	fi
-
+	# TODO add other supported architectures
+	ARCH=$(uname -m)
+    if [ "$ARCH" = "x86_64" ]; then
+    	ARCH=amd64
+    elif [ "$ARCH" = "aarch64" ]; then
+    	ARCH=arm64
+    else
+    	echo "Unsupported architechure"
+    	# exit 1
+    fi
 	set -- $dependencies
 	set -- $dependencies
 
 
 	${update_cmd}
 	${update_cmd}

+ 1 - 1
scripts/nm-upgrade-0-17-1-to-0-19-0.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
-LATEST="v0.20.5"
+LATEST="v0.20.6"
 INSTALL_PATH="/root"
 INSTALL_PATH="/root"
 
 
 trap restore_old_netmaker_instructions
 trap restore_old_netmaker_instructions

+ 574 - 0
scripts/nm-upgrade.sh

@@ -0,0 +1,574 @@
+#!/bin/bash
+
+CONFIG_FILE=netmaker.env
+# location of nm-quick.sh (usually `/root`)
+SCRIPT_DIR=$(dirname "$(realpath "$0")")
+CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
+NM_QUICK_VERSION="0.1.0"
+LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\")
+
+if [ "$(id -u)" -ne 0 ]; then
+	echo "This script must be run as root"
+	exit 1
+fi
+
+unset INSTALL_TYPE
+unset NETMAKER_BASE_DOMAIN
+
+# usage - displays usage instructions
+usage() {
+	echo "nm-upgrade.sh v$NM_QUICK_VERSION"
+	echo "usage: ./nm-upgrade.sh"
+	exit 1
+}
+
+while getopts v flag; do
+	case "${flag}" in
+	v)
+		usage
+		exit 0
+		;;
+	*)
+		usage
+		exit 0
+		;;
+	esac
+done
+
+# print_logo - prints the netmaker logo
+print_logo() {
+	cat <<"EOF"
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+                                                                                         
+ __   __     ______     ______   __    __     ______     __  __     ______     ______    
+/\ "-.\ \   /\  ___\   /\__  _\ /\ "-./  \   /\  __ \   /\ \/ /    /\  ___\   /\  == \   
+\ \ \-.  \  \ \  __\   \/_/\ \/ \ \ \-./\ \  \ \  __ \  \ \  _"-.  \ \  __\   \ \  __<   
+ \ \_\\"\_\  \ \_____\    \ \_\  \ \_\ \ \_\  \ \_\ \_\  \ \_\ \_\  \ \_____\  \ \_\ \_\ 
+  \/_/ \/_/   \/_____/     \/_/   \/_/  \/_/   \/_/\/_/   \/_/\/_/   \/_____/   \/_/ /_/ 
+                                                                                                                                                                                                 
+
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+EOF
+}
+
+# set_buildinfo - sets the information based on script input for how the installation should be run
+set_buildinfo() {
+
+	MASTERKEY=$(grep MASTER_KEY docker-compose.yml | awk '{print $2;}' | tr -d '"')
+	EMAIL=$(grep email Caddyfile | awk '{print $2;}' | tr -d '"')
+	BROKER=$(grep SERVER_NAME docker-compose.yml | awk '{print $2;}' | tr -d '"')
+	PREFIX="broker."
+	NETMAKER_BASE_DOMAIN=${BROKER/#$PREFIX}
+
+
+		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://app.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
+
+	echo "-----------Build Options-----------------------------"
+	echo "    EE or CE: $INSTALL_TYPE"
+	echo "   Version: $LATEST"
+	echo "   Installer: v$NM_QUICK_VERSION"
+	echo "-----------------------------------------------------"
+
+}
+
+# install_yq - install yq if not present
+install_yq() {
+	if ! command -v yq &>/dev/null; then
+		wget -qO /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 -qO /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
+}
+
+# install and run upgrade tool
+upgrade() {
+	wget -qO /tmp/nm-upgrade https://fileserver.netmaker.io/upgrade/nm-upgrade-${ARCH}
+	chmod +x /tmp/nm-upgrade
+	echo "generating netclient configuration files"
+	/tmp/nm-upgrade
+}
+
+# setup_netclient - installs netclient 
+setup_netclient() {
+	wget -qO netclient https://github.com/gravitl/netclient/releases/download/$LATEST/netclient-linux-$ARCH
+	chmod +x netclient
+	./netclient install -v 3
+}
+
+
+# wait_seconds - wait for the specified period of time
+wait_seconds() { (
+	for ((a = 1; a <= $1; a++)); do
+		echo ". . ."
+		sleep 1
+	done
+); }
+
+# confirm - get user input to confirm that they want to perform the next step
+confirm() { (
+	while true; do
+		read -p 'Does everything look right? [y/n]: ' yn
+		case $yn in
+		[Yy]*)
+			override="true"
+			break
+			;;
+		[Nn]*)
+			echo "exiting..."
+			exit 1
+			# TODO start from the beginning instead
+			;;
+		*) echo "Please answer yes or no." ;;
+		esac
+	done
+) }
+
+save_config() { (
+	echo "Saving the config to $CONFIG_PATH"
+	touch "$CONFIG_PATH"
+	save_config_item NM_EMAIL "$EMAIL"
+	save_config_item NM_DOMAIN "$NETMAKER_BASE_DOMAIN"
+	save_config_item UI_IMAGE_TAG "$LATEST"
+	# version-specific entries
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		save_config_item NETMAKER_TENANT_ID "$TENANT_ID"
+		save_config_item LICENSE_KEY "$LICENSE_KEY"
+		save_config_item METRICS_EXPORTER "on"
+		save_config_item PROMETHEUS "on"
+		save_config_item SERVER_IMAGE_TAG "$LATEST-ee"
+	else
+		save_config_item METRICS_EXPORTER "off"
+		save_config_item PROMETHEUS "off"
+		save_config_item SERVER_IMAGE_TAG "$LATEST"
+	fi
+	# copy entries from the previous config
+	local toCopy=("SERVER_HOST" "MASTER_KEY" "TURN_USERNAME" "TURN_PASSWORD" "MQ_USERNAME" "MQ_PASSWORD"
+		"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
+		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY"
+		"TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND"
+		"DISABLE_REMOTE_IP_CHECK" "NETCLIENT_ENDPOINT_DETECTION" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
+		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT")
+	for name in "${toCopy[@]}"; do
+		save_config_item $name "${!name}"
+	done
+	# preserve debug entries
+	if test -n "$NM_SKIP_BUILD"; then
+		save_config_item NM_SKIP_BUILD "$NM_SKIP_BUILD"
+	fi
+	if test -n "$NM_SKIP_CLONE"; then
+		save_config_item NM_SKIP_CLONE "$NM_SKIP_CLONE"
+	fi
+	if test -n "$NM_SKIP_DEPS"; then
+		save_config_item NM_SKIP_DEPS "$NM_SKIP_DEPS"
+	fi
+); }
+
+save_config_item() { (
+	local NAME="$1"
+	local VALUE="$2"
+	#echo "$NAME=$VALUE"
+	if test -z "$VALUE"; then
+		# load the default for empty values
+		VALUE=$(awk -F'=' "/^$NAME/ { print \$2}"  "$SCRIPT_DIR/netmaker.default.env")
+		# trim quotes for docker
+		VALUE=$(echo "$VALUE" | sed -E "s|^(['\"])(.*)\1$|\2|g")
+		#echo "Default for $NAME=$VALUE"
+	fi
+	# TODO single quote passwords
+	if grep -q "^$NAME=" "$CONFIG_PATH"; then
+		# TODO escape | in the value
+		sed -i "s|$NAME=.*|$NAME=$VALUE|" "$CONFIG_PATH"
+	else
+		echo "$NAME=$VALUE" >>"$CONFIG_PATH"
+	fi
+); }
+
+# install_dependencies - install necessary packages to run netmaker
+install_dependencies() {
+
+	if test -n "$NM_SKIP_DEPS"; then
+		return
+	fi
+
+	echo "checking dependencies..."
+
+	OS=$(uname)
+	if [ -f /etc/debian_version ]; then
+		dependencies="git wireguard wireguard-tools dnsutils jq docker.io docker-compose grep gawk"
+		update_cmd='apt update'
+		install_cmd='apt-get install -y'
+	elif [ -f /etc/alpine-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose grep gawk"
+		update_cmd='apk update'
+		install_cmd='apk --update add'
+	elif [ -f /etc/centos-release ]; then
+		dependencies="git wireguard jq bind-utils docker.io docker-compose grep gawk"
+		update_cmd='yum update'
+		install_cmd='yum install -y'
+	elif [ -f /etc/fedora-release ]; then
+		dependencies="git wireguard bind-utils jq docker.io docker-compose grep gawk"
+		update_cmd='dnf update'
+		install_cmd='dnf install -y'
+	elif [ -f /etc/redhat-release ]; then
+		dependencies="git wireguard jq docker.io bind-utils docker-compose grep gawk"
+		update_cmd='yum update'
+		install_cmd='yum install -y'
+	elif [ -f /etc/arch-release ]; then
+		dependencies="git wireguard-tools dnsutils jq docker.io docker-compose grep gawk"
+		update_cmd='pacman -Sy'
+		install_cmd='pacman -S --noconfirm'
+	elif [ "${OS}" = "FreeBSD" ]; then
+		dependencies="git wireguard wget jq docker.io docker-compose grep gawk"
+		update_cmd='pkg update'
+		install_cmd='pkg install -y'
+	else
+		install_cmd=''
+	fi
+
+	if [ -z "${install_cmd}" ]; then
+		echo "OS unsupported for automatic dependency install"
+		# TODO shouldnt exit, check if deps available, if not
+		#  ask the user to install manually and continue when ready
+		exit 1
+	fi
+	# TODO add other supported architectures
+	ARCH=$(uname -m)
+    if [ "$ARCH" = "x86_64" ]; then
+    	ARCH=amd64
+    elif [ "$ARCH" = "aarch64" ]; then
+    	ARCH=arm64
+    else
+    	echo "Unsupported architechure"
+    	# exit 1
+    fi
+	set -- $dependencies
+
+	${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
+				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)
+			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
+					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_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
+	if [ "$NETMAKER_BASE_DOMAIN" = "" ]; then
+		NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
+	fi
+	SERVER_HOST=$IP_ADDR
+	if test -z "$MASTER_KEY"; then
+		MASTER_KEY=$(
+			tr -dc A-Za-z0-9 </dev/urandom | head -c 30
+			echo ''
+		)
+	fi
+	DOMAIN_TYPE="auto"
+
+	echo "-----------------------------------------------------"
+	echo "The following subdomains will be used:"
+	echo "          dashboard.$NETMAKER_BASE_DOMAIN"
+	echo "                api.$NETMAKER_BASE_DOMAIN"
+	echo "             broker.$NETMAKER_BASE_DOMAIN"
+	echo "               turn.$NETMAKER_BASE_DOMAIN"
+	echo "            turnapi.$NETMAKER_BASE_DOMAIN"
+
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		echo "         prometheus.$NETMAKER_BASE_DOMAIN"
+		echo "  netmaker-exporter.$NETMAKER_BASE_DOMAIN"
+		echo "            grafana.$NETMAKER_BASE_DOMAIN"
+	fi
+
+	echo "-----------------------------------------------------"
+
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+
+		echo "-----------------------------------------------------"
+		echo "Provide Details for EE installation:"
+		echo "    1. Log into https://app.netmaker.io"
+		echo "    2. follow instructions to get a license at: https://docs.netmaker.io/ee/ee-setup.html"
+		echo "    3. Retrieve License and Tenant ID"
+		echo "    4. note email address"
+		echo "-----------------------------------------------------"
+		unset LICENSE_KEY
+		while [ -z "$LICENSE_KEY" ]; do
+			read -p "License Key: " LICENSE_KEY
+		done
+		unset TENANT_ID
+		while [ -z ${TENANT_ID} ]; do
+			read -p "Tenant ID: " TENANT_ID
+		done
+	fi
+
+	echo "using default username/random pass for MQ"
+	MQ_USERNAME="netmaker"
+	MQ_PASSWORD=$(
+		tr -dc A-Za-z0-9 </dev/urandom | head -c 30
+		echo ''
+	)
+
+	echo "using default username/random pass for TURN"
+	TURN_USERNAME="netmaker"
+	TURN_PASSWORD=$(
+		tr -dc A-Za-z0-9 </dev/urandom | head -c 30
+		echo ''
+	)
+	echo "-----------------------------------------------------------------"
+	echo "                SETUP ARGUMENTS"
+	echo "-----------------------------------------------------------------"
+	echo "        domain: $NETMAKER_BASE_DOMAIN"
+	echo "         email: $EMAIL"
+	echo "     public ip: $SERVER_HOST"
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		echo "       license: $LICENSE_KEY"
+		echo "    account id: $TENANT_ID"
+	fi
+	echo "-----------------------------------------------------------------"
+	echo "Confirm Settings for Installation"
+	echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
+
+	confirm
+}
+
+# install_netmaker - sets the config files and starts docker-compose
+install_netmaker() {
+
+	echo "-----------------------------------------------------------------"
+	echo "Beginning installation..."
+	echo "-----------------------------------------------------------------"
+
+	wait_seconds 3
+
+	echo "Pulling config files..."
+
+		local BASE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$LATEST"
+
+		local COMPOSE_URL="$BASE_URL/compose/docker-compose.yml"
+		local CADDY_URL="$BASE_URL/docker/Caddyfile"
+		if [ "$INSTALL_TYPE" = "ee" ]; then
+			local COMPOSE_OVERRIDE_URL="$BASE_URL/compose/docker-compose.ee.yml"
+			local CADDY_URL="$BASE_URL/docker/Caddyfile-EE"
+		fi
+		wget -qO "$SCRIPT_DIR"/docker-compose.yml $COMPOSE_URL
+		if test -n "$COMPOSE_OVERRIDE_URL"; then
+			wget -qO "$SCRIPT_DIR"/docker-compose.override.yml $COMPOSE_OVERRIDE_URL
+		fi
+		wget -qO "$SCRIPT_DIR"/Caddyfile "$CADDY_URL"
+		wget -qO "$SCRIPT_DIR"/netmaker.default.env "$BASE_URL/scripts/netmaker.default.env"
+		wget -qO "$SCRIPT_DIR"/mosquitto.conf "$BASE_URL/docker/mosquitto.conf"
+		wget -qO "$SCRIPT_DIR"/nm-certs.sh "$BASE_URL/scripts/nm-certs.sh"
+		wget -qO "$SCRIPT_DIR"/wait.sh "$BASE_URL/docker/wait.sh"
+
+	chmod +x "$SCRIPT_DIR"/wait.sh
+	mkdir -p /etc/netmaker
+
+	# link .env to the user config
+	ln -fs "$SCRIPT_DIR/netmaker.env" "$SCRIPT_DIR/.env"
+	save_config
+
+	# Fetch / update certs using certbot
+	chmod +x "$SCRIPT_DIR"/nm-certs.sh
+	"$SCRIPT_DIR"/nm-certs.sh
+
+	echo "Starting containers..."
+
+	# increase the timeouts
+	export DOCKER_CLIENT_TIMEOUT=120
+	export COMPOSE_HTTP_TIMEOUT=120
+
+	# start docker and rebuild containers / networks
+	docker-compose -f "$SCRIPT_DIR"/docker-compose.yml up -d --force-recreate
+
+	wait_seconds 2
+
+}
+
+# test_connection - tests to make sure Caddy has proper SSL certs
+test_connection() {
+
+	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 "-----------------------------------------------------------------"
+}
+
+cleanup() {
+	echo "Stopping all containers..."
+	local containers=("mq" "netmaker-ui" "coredns" "turn" "caddy" "netmaker" "netmaker-exporter" "prometheus" "grafana")
+	for name in "${containers[@]}"; do
+		local running=$(docker ps | grep -w "$name")
+		local exists=$(docker ps -a | grep -w "$name")
+		if test -n "$running"; then
+			docker stop "$name" 1>/dev/null
+		fi
+		if test -n "$exists"; then
+			docker rm "$name" 1>/dev/null
+		fi
+	done
+}
+
+# print netmaker logo
+print_logo
+
+# read the config
+if [ -f "$CONFIG_PATH" ]; then
+	echo "Using config: $CONFIG_PATH"
+	source "$CONFIG_PATH"
+	if [ "$UPGRADE_FLAG" = "yes" ]; then
+		INSTALL_TYPE="ee"
+	fi
+fi
+
+# setup the build instructions
+set_buildinfo
+
+set +e
+
+# install necessary packages
+install_dependencies
+
+# install yq if necessary
+install_yq
+
+set -e
+
+# get user input for variables
+set_install_vars
+
+set +e
+cleanup
+set -e
+
+# get upgrade tool and run
+upgrade
+
+# get and set config files, startup docker-compose
+install_netmaker
+
+set +e
+
+# make sure Caddy certs are working
+test_connection
+
+set -e
+
+# install netclient 
+setup_netclient
+
+
+# print success message
+print_success
+

+ 32 - 18
servercfg/serverconf.go

@@ -18,8 +18,9 @@ import (
 const EmqxBrokerType = "emqx"
 const EmqxBrokerType = "emqx"
 
 
 var (
 var (
-	Version = "dev"
-	Is_EE   = false
+	Version              = "dev"
+	Is_EE                = false
+	ErrLicenseValidation error
 )
 )
 
 
 // SetHost - sets the host ip
 // SetHost - sets the host ip
@@ -752,26 +753,28 @@ func GetNetworkLimit() int {
 	return networkslimit
 	return networkslimit
 }
 }
 
 
-// GetClientLimit - fetches free tier limits on ext. clients
-func GetClientLimit() int {
-	var clientsLimit int
-	if os.Getenv("CLIENTS_LIMIT") != "" {
-		clientsLimit, _ = strconv.Atoi(os.Getenv("CLIENTS_LIMIT"))
-	} else {
-		clientsLimit = config.Config.Server.ClientsLimit
+// GetMachinesLimit - fetches free tier limits on machines (clients + hosts)
+func GetMachinesLimit() int {
+	if l, err := strconv.Atoi(os.Getenv("MACHINES_LIMIT")); err == nil {
+		return l
 	}
 	}
-	return clientsLimit
+	return config.Config.Server.MachinesLimit
 }
 }
 
 
-// GetHostLimit - fetches free tier limits on hosts
-func GetHostLimit() int {
-	var hostsLimit int
-	if os.Getenv("HOSTS_LIMIT") != "" {
-		hostsLimit, _ = strconv.Atoi(os.Getenv("HOSTS_LIMIT"))
-	} else {
-		hostsLimit = config.Config.Server.HostsLimit
+// GetIngressLimit - fetches free tier limits on ingresses
+func GetIngressLimit() int {
+	if l, err := strconv.Atoi(os.Getenv("INGRESSES_LIMIT")); err == nil {
+		return l
+	}
+	return config.Config.Server.IngressesLimit
+}
+
+// GetEgressLimit - fetches free tier limits on egresses
+func GetEgressLimit() int {
+	if l, err := strconv.Atoi(os.Getenv("EGRESSES_LIMIT")); err == nil {
+		return l
 	}
 	}
-	return hostsLimit
+	return config.Config.Server.EgressesLimit
 }
 }
 
 
 // DeployedByOperator - returns true if the instance is deployed by netmaker operator
 // DeployedByOperator - returns true if the instance is deployed by netmaker operator
@@ -782,6 +785,17 @@ func DeployedByOperator() bool {
 	return config.Config.Server.DeployedByOperator
 	return config.Config.Server.DeployedByOperator
 }
 }
 
 
+// GetEnvironment returns the environment the server is running in (e.g. dev, staging, prod...)
+func GetEnvironment() string {
+	if env := os.Getenv("ENVIRONMENT"); env != "" {
+		return env
+	}
+	if env := config.Config.Server.Environment; env != "" {
+		return env
+	}
+	return ""
+}
+
 // parseStunList - turn string into slice of StunServers
 // parseStunList - turn string into slice of StunServers
 func parseStunList(stunString string) ([]models.StunServer, error) {
 func parseStunList(stunString string) ([]models.StunServer, error) {
 	var err error
 	var err error

+ 1 - 1
swagger.yaml

@@ -704,7 +704,7 @@ info:
 
 
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
     title: Netmaker
     title: Netmaker
-    version: 0.20.5
+    version: 0.20.6
 paths:
 paths:
     /api/dns:
     /api/dns:
         get:
         get: