Browse Source

Merge pull request #363 from gravitl/develop

Develop
Alex 3 years ago
parent
commit
0f608cb817
100 changed files with 3343 additions and 711 deletions
  1. 10 0
      .github/dependabot.yml
  2. 3 2
      .github/workflows/publish-docker.yml
  3. 4 10
      .github/workflows/test.yml
  4. 5 5
      README.md
  5. 1 1
      compose/docker-compose.caddy.yml
  6. 56 0
      compose/docker-compose.nodns.yml
  7. 1 1
      compose/docker-compose.yml
  8. 16 2
      config/config.go
  9. 1 1
      controllers/authGrpc.go
  10. 1 1
      controllers/common_test.go
  11. 4 2
      controllers/controller.go
  12. 11 3
      controllers/dnsHttpController.go
  13. 251 41
      controllers/dnsHttpController_test.go
  14. 7 3
      controllers/extClientHttpController.go
  15. 2 3
      controllers/fileHttpController.go
  16. 4 4
      controllers/networkHttpController.go
  17. 13 13
      controllers/networkHttpController_test.go
  18. 8 5
      controllers/nodeGrpcController.go
  19. 3 2
      controllers/nodeHttpController.go
  20. 5 0
      controllers/relay.go
  21. 1 1
      controllers/serverHttpController.go
  22. 53 31
      controllers/userHttpController.go
  23. 69 1
      controllers/userHttpController_test.go
  24. 54 2
      database/database.go
  25. 132 0
      database/postgres.go
  26. 4 4
      database/rqlite.go
  27. 6 6
      database/sqlite.go
  28. 5 0
      database/statics.go
  29. 4 0
      dnslogic/dns.go
  30. 26 0
      docker/Dockerfile-userspace
  31. BIN
      docs/_build/doctrees/architecture.doctree
  32. BIN
      docs/_build/doctrees/environment.pickle
  33. BIN
      docs/_build/doctrees/server-installation.doctree
  34. 1 1
      docs/_build/html/.buildinfo
  35. 5 5
      docs/_build/html/_sources/architecture.rst.txt
  36. 162 10
      docs/_build/html/_sources/server-installation.rst.txt
  37. 1 1
      docs/_build/html/_static/documentation_options.js
  38. 26 5
      docs/_build/html/about.html
  39. 26 5
      docs/_build/html/api.html
  40. 35 14
      docs/_build/html/architecture.html
  41. 26 5
      docs/_build/html/client-installation.html
  42. 26 5
      docs/_build/html/conduct.html
  43. 13 6
      docs/_build/html/external-clients.html
  44. 13 6
      docs/_build/html/genindex.html
  45. 26 5
      docs/_build/html/getting-started.html
  46. 15 7
      docs/_build/html/index.html
  47. 13 6
      docs/_build/html/install.html
  48. 26 5
      docs/_build/html/license.html
  49. BIN
      docs/_build/html/objects.inv
  50. 26 5
      docs/_build/html/quick-start-nginx.html
  51. 29 8
      docs/_build/html/quick-start.html
  52. 13 6
      docs/_build/html/search.html
  53. 0 0
      docs/_build/html/searchindex.js
  54. 190 14
      docs/_build/html/server-installation.html
  55. 26 5
      docs/_build/html/support.html
  56. 26 5
      docs/_build/html/troubleshoot.html
  57. 26 5
      docs/_build/html/usage.html
  58. 5 5
      docs/architecture.rst
  59. 1 1
      docs/conf.py
  60. 162 10
      docs/server-installation.rst
  61. 51 4
      functions/helpers.go
  62. 3 1
      functions/jwt.go
  63. 25 25
      functions/local.go
  64. 1 0
      go.mod
  65. 19 0
      go.sum
  66. 1 0
      logic/extpeers.go
  67. 110 0
      logic/network.go
  68. 5 1
      logic/nodes.go
  69. 474 0
      logic/server.go
  70. 47 0
      logic/serverconf.go
  71. 57 13
      logic/util.go
  72. 334 0
      logic/wireguard.go
  73. 3 2
      main.go
  74. 17 17
      models/accessToken.go
  75. 2 4
      models/extclient.go
  76. 14 14
      models/intclient.go
  77. 46 37
      models/network.go
  78. 3 2
      models/node.go
  79. 16 0
      models/structs.go
  80. 2 0
      models/validation.go
  81. 5 1
      netclient/auth/auth.go
  82. 4 4
      netclient/command/commands.go
  83. 20 9
      netclient/config/config.go
  84. 3 3
      netclient/daemon/macos.go
  85. 28 32
      netclient/daemon/systemd.go
  86. 5 0
      netclient/daemon/windows.go
  87. 42 54
      netclient/functions/checkin.go
  88. 10 34
      netclient/functions/common.go
  89. 46 52
      netclient/functions/join.go
  90. 128 0
      netclient/functions/list.go
  91. 2 0
      netclient/local/dns.go
  92. 10 3
      netclient/local/local.go
  93. 7 2
      netclient/main.go
  94. 45 5
      netclient/ncutils/netclientutils.go
  95. 1 1
      netclient/ncwindows/windows.go
  96. 33 91
      netclient/server/grpc.go
  97. 5 1
      netclient/wireguard/common.go
  98. 7 2
      netclient/wireguard/unix.go
  99. 1 0
      relay/relay.go
  100. 33 3
      scripts/netclient-install.sh

+ 10 - 0
.github/dependabot.yml

@@ -0,0 +1,10 @@
+# Basic dependabot.yml file with minimum configuration for gomod.
+
+version: 2
+updates:
+  # Enable version updates for go
+  - package-ecosystem: "gomod"
+    directory: "/"
+    # Check for updates every day (weekdays)
+    schedule:
+      interval: "daily"

+ 3 - 2
.github/workflows/publish-docker.yml

@@ -31,7 +31,8 @@ jobs:
         uses: docker/setup-qemu-action@v1
       - name: Set up Docker Buildx
         uses: docker/setup-buildx-action@v1
-      - name: Login to DockerHub
+      - if: github.event_name != 'pull_request'
+        name: Login to DockerHub
         uses: docker/login-action@v1
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -41,5 +42,5 @@ jobs:
         with:
           context: .
           platforms: linux/amd64, linux/arm64
-          push: true
+          push: ${{ github.event_name != 'pull_request' }}
           tags: gravitl/netmaker:${{ env.TAG }}

+ 4 - 10
.github/workflows/test.yml

@@ -6,14 +6,8 @@ on:
 jobs:
   tests:
     env:
-      DATABASE: rqlite
-    runs-on: ubuntu-latest
-    services:
-      rqlite:
-        image: rqlite/rqlite
-        ports:
-            - 4001:4001
-            - 4002:4002
+      DATABASE: sqlite
+    runs-on: ubuntu-20.04
     steps:
       - name: Checkout
         uses: actions/checkout@v2
@@ -21,5 +15,5 @@ jobs:
         run: |
             go test -p 1 ./... -v
         env:
-          DATABASE: rqlite
-          CLIENT_MODE: "off"
+          DATABASE: sqlite
+          CLIENT_MODE: "off"

+ 5 - 5
README.md

@@ -8,13 +8,13 @@
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/docker/v/gravitl/netmaker?color=blue" />
+    <img src="https://img.shields.io/badge/Version-0.8.4-informational?style=flat-square" />
   </a>
   <a href="https://discord.gg/zRb9Vfhk8A">
-    <img src="https://img.shields.io/badge/community-discord-purple" />
+    <img src="https://img.shields.io/badge/community-discord-informational" />
   </a>
   <a href="https://github.com/gravitl/netmaker/graphs/contributors">
-    <img src="https://img.shields.io/github/commit-activity/w/gravitl/netmaker?color=brightgreen" />
+    <img src="https://img.shields.io/github/commit-activity/w/gravitl/netmaker?color=blue" />
   </a>
   <a href="https://gravitl.com/resources">
     <img src="https://img.shields.io/badge/learning-resources-9cf" />
@@ -27,7 +27,6 @@
   </a>
 </p>
 
-
 # WireGuard® Automation from Homelab to Enterprise
 - [x] Peer-to-Peer Mesh Networks
 - [x] Site-to-Site Gateways
@@ -37,7 +36,8 @@
 
 # Get Started in 5 Minutes
 
-**For production-grade installations, visit the [Install Docs](https://netmaker.readthedocs.io/en/develop/install.html).**
+**For production-grade installations, visit the [Install Docs](https://netmaker.readthedocs.io/en/develop/install.html).**  
+**For an HA install using helm on k8s, visit the [Helm Repo](https://github.com/gravitl/netmaker-helm/).**
 1. Get a cloud VM with Ubuntu 20.04 and a public IP.
 2. Open ports 443, 53, and 51821-51830/udp on the VM firewall and in cloud security settings.
 3. Run the script:

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

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.8.3
+    image: gravitl/netmaker:v0.8.4
     volumes:
       - /etc/netclient/config:/etc/netclient/config
       - dnsconfig:/root/config/dnsconfig

+ 56 - 0
compose/docker-compose.nodns.yml

@@ -0,0 +1,56 @@
+version: "3.4"
+
+services:
+  netmaker:
+    container_name: netmaker
+    image: gravitl/netmaker:v0.8.4
+    volumes:
+      - /etc/netclient/config:/etc/netclient/config
+      - /usr/bin/wg:/usr/bin/wg
+      - sqldata:/root/data
+    cap_add: 
+      - NET_ADMIN
+    restart: always
+    network_mode: host
+    environment:
+      SERVER_HOST: "SERVER_PUBLIC_IP"
+      SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
+      SERVER_GRPC_CONN_STRING: "grpc.NETMAKER_BASE_DOMAIN:443"
+      COREDNS_ADDR: "SERVER_PUBLIC_IP"
+      GRPC_SSL: "on"
+      DNS_MODE: "off"
+      SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
+      SERVER_GRPC_HOST: "grpc.NETMAKER_BASE_DOMAIN"
+      API_PORT: "8081"
+      GRPC_PORT: "50051"
+      CLIENT_MODE: "on"
+      MASTER_KEY: "REPLACE_MASTER_KEY"
+      SERVER_GRPC_WIREGUARD: "off"
+      CORS_ALLOWED_ORIGIN: "*"
+      DATABASE: "sqlite"
+  netmaker-ui:
+    container_name: netmaker-ui
+    depends_on:
+      - netmaker
+    image: gravitl/netmaker-ui:v0.8
+    links:
+      - "netmaker:api"
+    ports:
+      - "8082:80"
+    environment:
+      BACKEND_URL: "https://api.NETMAKER_BASE_DOMAIN"
+    restart: always
+  caddy:
+    image: caddy:latest
+    container_name: caddy
+    restart: unless-stopped
+    network_mode: host # Wants ports 80 and 443!
+    volumes:
+      - /root/Caddyfile:/etc/caddy/Caddyfile
+      # - $PWD/site:/srv # you could also serve a static site in site folder
+      - caddy_data:/data
+      - caddy_conf:/config
+volumes:
+  caddy_data: {}
+  caddy_conf: {}
+  sqldata: {}

+ 1 - 1
compose/docker-compose.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.8.3
+    image: gravitl/netmaker:v0.8.4
     volumes:
       - /etc/netclient/config:/etc/netclient/config
       - dnsconfig:/root/config/dnsconfig

+ 16 - 2
config/config.go

@@ -30,6 +30,7 @@ var Config *EnvironmentConfig
 // EnvironmentConfig :
 type EnvironmentConfig struct {
 	Server ServerConfig `yaml:"server"`
+	SQL SQLConfig `yaml:"sql"`
 }
 
 // ServerConfig :
@@ -44,21 +45,34 @@ type ServerConfig struct {
 	GRPCSecure           string `yaml:"grpcsecure"`
 	MasterKey            string `yaml:"masterkey"`
 	AllowedOrigin        string `yaml:"allowedorigin"`
+	NodeID        string `yaml:"nodeid"`
 	RestBackend          string `yaml:"restbackend"`
 	AgentBackend         string `yaml:"agentbackend"`
 	ClientMode           string `yaml:"clientmode"`
 	DNSMode              string `yaml:"dnsmode"`
-	SplitDNS           string `yaml:"splitdns"`
+	SplitDNS             string `yaml:"splitdns"`
 	DisableRemoteIPCheck string `yaml:"disableremoteipcheck"`
 	DisableDefaultNet    string `yaml:"disabledefaultnet"`
 	GRPCSSL              string `yaml:"grpcssl"`
 	Version              string `yaml:"version"`
 	SQLConn              string `yaml:"sqlconn"`
-	Platform              string `yaml:"platform"`
+	Platform             string `yaml:"platform"`
 	Database             string `yaml:database`
 	CheckinInterval      string `yaml:checkininterval`
 	DefaultNodeLimit     int32  `yaml:"defaultnodelimit"`
 	Verbosity            int32  `yaml:"verbosity"`
+	ServerCheckinInterval int64  `yaml:"servercheckininterval"`
+}
+
+
+// Generic SQL Config
+type SQLConfig struct {
+	Host string `yaml:"host"`
+	Port int32 `yaml:"port"`
+	Username string `yaml:"username"`
+	Password string `yaml:"password"`
+	DB string `yaml:"db"`
+	SSLMode string `yaml:"sslmode"`
 }
 
 //reading in the env file

+ 1 - 1
controllers/authGrpc.go

@@ -124,7 +124,7 @@ func (s *NodeServiceServer) Login(ctx context.Context, req *nodepb.Object) (*nod
 		err = errors.New("Missing Password.")
 		return nil, err
 	} else {
-		//Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API untill approved).
+		//Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API until approved).
 		collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
 		if err != nil {
 			return nil, err

+ 1 - 1
controllers/common_test.go

@@ -124,7 +124,7 @@ func TestSetNetworkNodesLastModified(t *testing.T) {
 }
 
 func createTestNode() models.Node {
-	createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Endpoint: "10.0.0.1", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet"}
+	createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: "testnode", Endpoint: "10.0.0.1", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet"}
 	node, _ := logic.CreateNode(createnode, "skynet")
 	return node
 }

+ 4 - 2
controllers/controller.go

@@ -7,11 +7,13 @@ import (
 	"os"
 	"os/signal"
 	"sync"
+
 	"github.com/gorilla/handlers"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/servercfg"
 )
 
+// HandleRESTRequests - handles the rest requests
 func HandleRESTRequests(wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -30,7 +32,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
 	fileHandlers(r)
 	serverHandlers(r)
 	extClientHandlers(r)
-	
+
 	port := servercfg.GetAPIPort()
 
 	srv := &http.Server{Addr: ":" + port, Handler: handlers.CORS(originsOk, headersOk, methodsOk)(r)}
@@ -41,7 +43,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
 		}
 	}()
 
-	log.Println("REST Server succesfully started on port " + port + " (REST)")
+	log.Println("REST Server successfully started on port " + port + " (REST)")
 	c := make(chan os.Signal)
 
 	// Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)

+ 11 - 3
controllers/dnsHttpController.go

@@ -56,6 +56,7 @@ func getAllDNS(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(dns)
 }
 
+// GetAllDNS - gets all dns entries
 func GetAllDNS() ([]models.DNSEntry, error) {
 	var dns []models.DNSEntry
 	networks, err := models.GetNetworks()
@@ -72,6 +73,7 @@ func GetAllDNS() ([]models.DNSEntry, error) {
 	return dns, nil
 }
 
+// GetNodeDNS - gets node dns
 func GetNodeDNS(network string) ([]models.DNSEntry, error) {
 
 	var dns []models.DNSEntry
@@ -114,6 +116,7 @@ func getCustomDNS(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(dns)
 }
 
+// GetDNSEntryNum - gets which entry the dns was
 func GetDNSEntryNum(domain string, network string) (int, error) {
 
 	num := 0
@@ -133,7 +136,7 @@ func GetDNSEntryNum(domain string, network string) (int, error) {
 	return num, nil
 }
 
-//Gets all nodes associated with network, including pending nodes
+// Gets all nodes associated with network, including pending nodes
 func getDNS(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
@@ -202,7 +205,7 @@ func updateDNS(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
 	}
-	//fill in any missing fields
+	// fill in any missing fields
 	if dnschange.Name == "" {
 		dnschange.Name = entry.Name
 	}
@@ -257,6 +260,7 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(entrytext + " deleted.")
 }
 
+// CreateDNS - creates a DNS entry
 func CreateDNS(entry models.DNSEntry) (models.DNSEntry, error) {
 
 	data, err := json.Marshal(&entry)
@@ -272,6 +276,7 @@ func CreateDNS(entry models.DNSEntry) (models.DNSEntry, error) {
 	return entry, err
 }
 
+// GetDNSEntry - gets a DNS entry
 func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
 	var entry models.DNSEntry
 	key, err := functions.GetRecordKey(domain, network)
@@ -286,6 +291,7 @@ func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
 	return entry, err
 }
 
+// UpdateDNS - updates DNS entry
 func UpdateDNS(dnschange models.DNSEntry, entry models.DNSEntry) (models.DNSEntry, error) {
 
 	key, err := functions.GetRecordKey(entry.Name, entry.Network)
@@ -308,9 +314,9 @@ func UpdateDNS(dnschange models.DNSEntry, entry models.DNSEntry) (models.DNSEntr
 	data, err := json.Marshal(&entry)
 	err = database.Insert(newkey, string(data), database.DNS_TABLE_NAME)
 	return entry, err
-
 }
 
+// DeleteDNS - deletes a DNS entry
 func DeleteDNS(domain string, network string) error {
 	key, err := functions.GetRecordKey(domain, network)
 	if err != nil {
@@ -334,6 +340,7 @@ func pushDNS(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode("DNS Pushed to CoreDNS")
 }
 
+// ValidateDNSCreate - checks if an entry is valid
 func ValidateDNSCreate(entry models.DNSEntry) error {
 
 	v := validator.New()
@@ -357,6 +364,7 @@ func ValidateDNSCreate(entry models.DNSEntry) error {
 	return err
 }
 
+// ValidateDNSUpdate - validates a DNS update
 func ValidateDNSUpdate(change models.DNSEntry, entry models.DNSEntry) error {
 
 	v := validator.New()

+ 251 - 41
controllers/dnsHttpController_test.go

@@ -1,100 +1,309 @@
 package controller
 
 import (
+	"io/ioutil"
+	"os"
 	"testing"
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/dnslogic"
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 )
 
+func TestGetAllDNS(t *testing.T) {
+	database.InitializeDatabase()
+	deleteAllDNS(t)
+	deleteAllNetworks()
+	createNet()
+	t.Run("NoEntries", func(t *testing.T) {
+		entries, err := GetAllDNS()
+		assert.Nil(t, err)
+		assert.Equal(t, []models.DNSEntry(nil), entries)
+	})
+	t.Run("OneEntry", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
+		CreateDNS(entry)
+		entries, err := GetAllDNS()
+		assert.Nil(t, err)
+		assert.Equal(t, 1, len(entries))
+	})
+	t.Run("MultipleEntry", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.7", "anotherhost", "skynet"}
+		CreateDNS(entry)
+		entries, err := GetAllDNS()
+		assert.Nil(t, err)
+		assert.Equal(t, 2, len(entries))
+	})
+}
+
 func TestGetNodeDNS(t *testing.T) {
 	database.InitializeDatabase()
+	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	createTestNode()
-	dns, err := GetNodeDNS("skynet")
-	assert.Nil(t, err)
-	t.Log(dns)
+	t.Run("NoNodes", func(t *testing.T) {
+		dns, err := GetNodeDNS("skynet")
+		assert.EqualError(t, err, "could not find any records")
+		assert.Equal(t, []models.DNSEntry(nil), dns)
+	})
+	t.Run("NodeExists", func(t *testing.T) {
+		createTestNode()
+		dns, err := GetNodeDNS("skynet")
+		assert.Nil(t, err)
+		assert.Equal(t, "10.0.0.1", dns[0].Address)
+	})
+	t.Run("MultipleNodes", func(t *testing.T) {
+		createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Endpoint: "10.100.100.3", MacAddress: "01:02:03:04:05:07", Password: "password", Network: "skynet"}
+		_, err := logic.CreateNode(createnode, "skynet")
+		assert.Nil(t, err)
+		dns, err := GetNodeDNS("skynet")
+		assert.Nil(t, err)
+		assert.Equal(t, 2, len(dns))
+	})
 }
 func TestGetCustomDNS(t *testing.T) {
-	t.Skip()
 	database.InitializeDatabase()
+	deleteAllDNS(t)
 	deleteAllNetworks()
-	createNet()
-	createTestNode()
-	dns, err := dnslogic.GetCustomDNS("skynet")
-	assert.Nil(t, err)
-	t.Log(dns)
+	t.Run("NoNetworks", func(t *testing.T) {
+		dns, err := dnslogic.GetCustomDNS("skynet")
+		assert.EqualError(t, err, "could not find any records")
+		assert.Equal(t, []models.DNSEntry(nil), dns)
+	})
+	t.Run("NoNodes", func(t *testing.T) {
+		createNet()
+		dns, err := dnslogic.GetCustomDNS("skynet")
+		assert.EqualError(t, err, "could not find any records")
+		assert.Equal(t, []models.DNSEntry(nil), dns)
+	})
+	t.Run("NodeExists", func(t *testing.T) {
+		createTestNode()
+		dns, err := dnslogic.GetCustomDNS("skynet")
+		assert.EqualError(t, err, "could not find any records")
+		assert.Equal(t, 0, len(dns))
+	})
+	t.Run("EntryExist", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
+		CreateDNS(entry)
+		dns, err := dnslogic.GetCustomDNS("skynet")
+		assert.Nil(t, err)
+		assert.Equal(t, 1, len(dns))
+	})
+	t.Run("MultipleEntries", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.4", "host4", "skynet"}
+		CreateDNS(entry)
+		dns, err := dnslogic.GetCustomDNS("skynet")
+		assert.Nil(t, err)
+		assert.Equal(t, 2, len(dns))
+	})
 }
+
 func TestGetDNSEntryNum(t *testing.T) {
 	database.InitializeDatabase()
+	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	createTestNode()
-	num, err := GetDNSEntryNum("myhost", "skynet")
-	assert.Nil(t, err)
-	t.Log(num)
+	t.Run("NoNodes", func(t *testing.T) {
+		num, err := GetDNSEntryNum("myhost", "skynet")
+		assert.Nil(t, err)
+		assert.Equal(t, 0, num)
+	})
+	t.Run("NodeExists", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+		_, err := CreateDNS(entry)
+		assert.Nil(t, err)
+		num, err := GetDNSEntryNum("newhost", "skynet")
+		assert.Nil(t, err)
+		assert.Equal(t, 1, num)
+	})
 }
 func TestGetDNS(t *testing.T) {
 	database.InitializeDatabase()
+	deleteAllDNS(t)
 	deleteAllNetworks()
-	dns, err := dnslogic.GetDNS("skynet")
-	assert.Nil(t, err)
-	t.Log(dns)
+	createNet()
+	t.Run("NoEntries", func(t *testing.T) {
+		dns, err := dnslogic.GetDNS("skynet")
+		assert.Nil(t, err)
+		assert.Nil(t, dns)
+	})
+	t.Run("CustomDNSExists", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+		_, err := CreateDNS(entry)
+		assert.Nil(t, err)
+		dns, err := dnslogic.GetDNS("skynet")
+		t.Log(dns)
+		assert.Nil(t, err)
+		assert.NotNil(t, dns)
+		assert.Equal(t, "skynet", dns[0].Network)
+		assert.Equal(t, 1, len(dns))
+	})
+	t.Run("NodeExists", func(t *testing.T) {
+		deleteAllDNS(t)
+		createTestNode()
+		dns, err := dnslogic.GetDNS("skynet")
+		assert.Nil(t, err)
+		assert.NotNil(t, dns)
+		assert.Equal(t, "skynet", dns[0].Network)
+		assert.Equal(t, 1, len(dns))
+	})
+	t.Run("NodeAndCustomDNS", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+		_, err := CreateDNS(entry)
+		dns, err := dnslogic.GetDNS("skynet")
+		t.Log(dns)
+		assert.Nil(t, err)
+		assert.NotNil(t, dns)
+		assert.Equal(t, "skynet", dns[0].Network)
+		assert.Equal(t, "skynet", dns[1].Network)
+		assert.Equal(t, 2, len(dns))
+	})
 }
+
 func TestCreateDNS(t *testing.T) {
 	database.InitializeDatabase()
-	deleteAllNetworks()
 	deleteAllDNS(t)
+	deleteAllNetworks()
 	createNet()
-	//dns, err := GetDNS("skynet")
-	//assert.Nil(t, err)
-	//for _, entry := range dns {
-	//	_, _ = DeleteDNS(entry.Name, "skynet")
-	//}
 	entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
-	err := ValidateDNSCreate(entry)
-	assert.Nil(t, err)
-	if err != nil {
-		t.Log(err)
-	}
 	dns, err := CreateDNS(entry)
 	assert.Nil(t, err)
-	t.Log(dns)
+	assert.Equal(t, "newhost", dns.Name)
+}
+
+func TestSetDNS(t *testing.T) {
+	database.InitializeDatabase()
+	deleteAllDNS(t)
+	deleteAllNetworks()
+	t.Run("NoNetworks", func(t *testing.T) {
+		err := dnslogic.SetDNS()
+		assert.Nil(t, err)
+		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
+		assert.Nil(t, err)
+		assert.False(t, info.IsDir())
+		assert.Equal(t, int64(0), info.Size())
+	})
+	t.Run("NoEntries", func(t *testing.T) {
+		createNet()
+		err := dnslogic.SetDNS()
+		assert.Nil(t, err)
+		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
+		assert.Nil(t, err)
+		assert.False(t, info.IsDir())
+		assert.Equal(t, int64(0), info.Size())
+	})
+	t.Run("NodeExists", func(t *testing.T) {
+		createTestNode()
+		err := dnslogic.SetDNS()
+		assert.Nil(t, err)
+		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
+		assert.Nil(t, err)
+		assert.False(t, info.IsDir())
+		content, err := ioutil.ReadFile("./config/dnsconfig/netmaker.hosts")
+		assert.Nil(t, err)
+		assert.Contains(t, string(content), "testnode.skynet")
+	})
+	t.Run("EntryExists", func(t *testing.T) {
+		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
+		CreateDNS(entry)
+		err := dnslogic.SetDNS()
+		assert.Nil(t, err)
+		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
+		assert.Nil(t, err)
+		assert.False(t, info.IsDir())
+		content, err := ioutil.ReadFile("./config/dnsconfig/netmaker.hosts")
+		assert.Nil(t, err)
+		assert.Contains(t, string(content), "newhost.skynet")
+	})
+
 }
+
 func TestGetDNSEntry(t *testing.T) {
 	database.InitializeDatabase()
+	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
 	createTestNode()
 	entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
 	CreateDNS(entry)
-	entry, err := GetDNSEntry("newhost", "skynet")
-	assert.Nil(t, err)
-	t.Log(entry)
+	t.Run("wrong net", func(t *testing.T) {
+		entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat")
+		assert.EqualError(t, err, "no result found")
+		assert.Equal(t, models.DNSEntry{}, entry)
+	})
+	t.Run("wrong host", func(t *testing.T) {
+		entry, err := GetDNSEntry("badhost", "skynet")
+		assert.EqualError(t, err, "no result found")
+		assert.Equal(t, models.DNSEntry{}, entry)
+	})
+	t.Run("good host", func(t *testing.T) {
+		entry, err := GetDNSEntry("newhost", "skynet")
+		assert.Nil(t, err)
+		assert.Equal(t, "newhost", entry.Name)
+	})
+	t.Run("node", func(t *testing.T) {
+		entry, err := GetDNSEntry("testnode", "skynet")
+		assert.EqualError(t, err, "no result found")
+		assert.Equal(t, models.DNSEntry{}, entry)
+	})
 }
 func TestUpdateDNS(t *testing.T) {
+	var newentry models.DNSEntry
 	database.InitializeDatabase()
+	deleteAllDNS(t)
+	deleteAllNetworks()
+	createNet()
+	entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+	CreateDNS(entry)
+	t.Run("change address", func(t *testing.T) {
+		newentry.Address = "10.0.0.75"
+		updated, err := UpdateDNS(newentry, entry)
+		assert.Nil(t, err)
+		assert.Equal(t, newentry.Address, updated.Address)
+	})
+	t.Run("change name", func(t *testing.T) {
+		newentry.Name = "newname"
+		updated, err := UpdateDNS(newentry, entry)
+		assert.Nil(t, err)
+		assert.Equal(t, newentry.Name, updated.Name)
+	})
+	t.Run("change network", func(t *testing.T) {
+		newentry.Network = "wirecat"
+		updated, err := UpdateDNS(newentry, entry)
+		assert.Nil(t, err)
+		assert.NotEqual(t, newentry.Network, updated.Network)
+	})
 }
 func TestDeleteDNS(t *testing.T) {
 	database.InitializeDatabase()
+	deleteAllDNS(t)
+	deleteAllNetworks()
+	createNet()
+	entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+	CreateDNS(entry)
 	t.Run("EntryExists", func(t *testing.T) {
-		err := DeleteDNS("myhost", "skynet")
+		err := DeleteDNS("newhost", "skynet")
 		assert.Nil(t, err)
 	})
-	t.Run("NoEntry", func(t *testing.T) {
+	t.Run("NodeExists", func(t *testing.T) {
 		err := DeleteDNS("myhost", "skynet")
 		assert.Nil(t, err)
 	})
 
+	t.Run("NoEntries", func(t *testing.T) {
+		err := DeleteDNS("myhost", "skynet")
+		assert.Nil(t, err)
+	})
 }
 
 func TestValidateDNSUpdate(t *testing.T) {
 	database.InitializeDatabase()
+	deleteAllDNS(t)
+	deleteAllNetworks()
+	createNet()
 	entry := models.DNSEntry{"10.0.0.2", "myhost", "skynet"}
-	_ = DeleteDNS("mynode", "skynet")
 	t.Run("BadNetwork", func(t *testing.T) {
 		change := models.DNSEntry{"10.0.0.2", "myhost", "badnet"}
 		err := ValidateDNSUpdate(change, entry)
@@ -140,11 +349,14 @@ func TestValidateDNSUpdate(t *testing.T) {
 	})
 	t.Run("NameUnique", func(t *testing.T) {
 		change := models.DNSEntry{"10.0.0.2", "myhost", "wirecat"}
-		_, _ = CreateDNS(entry)
-		_, _ = CreateDNS(change)
+		CreateDNS(entry)
+		CreateDNS(change)
 		err := ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag")
+		//cleanup
+		err = DeleteDNS("myhost", "wirecat")
+		assert.Nil(t, err)
 	})
 
 }
@@ -196,11 +408,9 @@ func TestValidateDNSCreate(t *testing.T) {
 
 func deleteAllDNS(t *testing.T) {
 	dns, err := GetAllDNS()
-	t.Log(err)
-	t.Log(dns)
+	assert.Nil(t, err)
 	for _, record := range dns {
-		t.Log(dns)
 		err := DeleteDNS(record.Name, record.Network)
-		t.Log(err)
+		assert.Nil(t, err)
 	}
 }

+ 7 - 3
controllers/extClientHttpController.go

@@ -56,6 +56,7 @@ func getNetworkExtClients(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(extclients)
 }
 
+// GetNetworkExtClients - gets the ext clients of given network
 func GetNetworkExtClients(network string) ([]models.ExtClient, error) {
 	var extclients []models.ExtClient
 
@@ -130,6 +131,7 @@ func getExtClient(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(client)
 }
 
+// GetExtClient - gets a single ext client on a network
 func GetExtClient(clientid string, network string) (models.ExtClient, error) {
 	var extclient models.ExtClient
 	key, err := functions.GetRecordKey(clientid, network)
@@ -238,6 +240,7 @@ Endpoint = %s
 	json.NewEncoder(w).Encode(client)
 }
 
+// CreateExtClient - creates an extclient
 func CreateExtClient(extclient models.ExtClient) error {
 	if extclient.PrivateKey == "" {
 		privateKey, err := wgtypes.GeneratePrivateKey()
@@ -351,6 +354,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(newclient)
 }
 
+// UpdateExtClient - only supports name changes right now
 func UpdateExtClient(newclientid string, network string, client models.ExtClient) (models.ExtClient, error) {
 
 	err := DeleteExtClient(network, client.ClientID)
@@ -362,6 +366,7 @@ func UpdateExtClient(newclientid string, network string, client models.ExtClient
 	return client, err
 }
 
+// DeleteExtClient - deletes an existing ext client
 func DeleteExtClient(network string, clientid string) error {
 	key, err := functions.GetRecordKey(clientid, network)
 	if err != nil {
@@ -371,9 +376,7 @@ func DeleteExtClient(network string, clientid string) error {
 	return err
 }
 
-/**
- * Deletes ext clients based on gateway (mac) of ingress node and network
- */
+// DeleteGatewayExtClients - deletes ext clients based on gateway (mac) of ingress node and network
 func DeleteGatewayExtClients(gatewayID string, networkName string) error {
 	currentExtClients, err := GetNetworkExtClients(networkName)
 	if err != nil && !database.IsEmptyRecord(err) {
@@ -411,6 +414,7 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 	returnSuccessResponse(w, r, params["clientid"]+" deleted.")
 }
 
+// StringWithCharset - returns a random string in a charset
 func StringWithCharset(length int, charset string) string {
 	b := make([]byte, length)
 	for i := range b {

+ 2 - 3
controllers/fileHttpController.go

@@ -1,11 +1,10 @@
 package controller
 
 import (
-    "net/http"
-    "github.com/gorilla/mux"
+	"github.com/gorilla/mux"
+	"net/http"
 )
 
-
 func fileHandlers(r *mux.Router) {
 	r.PathPrefix("/meshclient/files").Handler(http.StripPrefix("/meshclient/files", http.FileServer(http.Dir("./meshclient/files"))))
 }

+ 4 - 4
controllers/networkHttpController.go

@@ -344,14 +344,14 @@ func DeleteNetwork(network string) error {
 		servers, err := logic.GetSortedNetworkServerNodes(network)
 		if err == nil {
 			for _, s := range servers {
-				if err = logic.DeleteNode(s.ID, true); err != nil {
-					functions.PrintUserLog("[netmaker]", "could not removed server "+s.Name+" before deleting network "+network, 2)
+				if err = logic.DeleteNode(&s, true); err != nil {
+					functions.PrintUserLog("", "could not removed server "+s.Name+" before deleting network "+network, 2)
 				} else {
-					functions.PrintUserLog("[netmaker]", "removed server "+s.Name+" before deleting network "+network, 2)
+					functions.PrintUserLog("", "removed server "+s.Name+" before deleting network "+network, 2)
 				}
 			}
 		} else {
-			functions.PrintUserLog("[netmaker]", "could not remove servers before deleting network "+network, 1)
+			functions.PrintUserLog("", "could not remove servers before deleting network "+network, 1)
 		}
 		return database.DeleteRecord(database.NETWORKS_TABLE_NAME, network)
 	}

+ 13 - 13
controllers/networkHttpController_test.go

@@ -228,14 +228,14 @@ func TestValidateNetworkUpdate(t *testing.T) {
 
 	//DeleteNetworks
 	cases := []NetworkValidationTestCase{
-		NetworkValidationTestCase{
+		{
 			testname: "InvalidAddress",
 			network: models.Network{
 				AddressRange: "10.0.0.256",
 			},
 			errMessage: "Field validation for 'AddressRange' failed on the 'cidr' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "InvalidAddress6",
 			network: models.Network{
 				AddressRange6: "2607::ag",
@@ -243,77 +243,77 @@ func TestValidateNetworkUpdate(t *testing.T) {
 			errMessage: "Field validation for 'AddressRange6' failed on the 'cidr' tag",
 		},
 
-		NetworkValidationTestCase{
+		{
 			testname: "BadDisplayName",
 			network: models.Network{
 				DisplayName: "skynet*",
 			},
 			errMessage: "Field validation for 'DisplayName' failed on the 'alphanum' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "DisplayNameTooLong",
 			network: models.Network{
 				DisplayName: "Thisisareallylongdisplaynamethatistoolong",
 			},
 			errMessage: "Field validation for 'DisplayName' failed on the 'max' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "DisplayNameTooShort",
 			network: models.Network{
 				DisplayName: "1",
 			},
 			errMessage: "Field validation for 'DisplayName' failed on the 'min' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "InvalidNetID",
 			network: models.Network{
 				NetID: "contains spaces",
 			},
 			errMessage: "Field validation for 'NetID' failed on the 'alphanum' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "NetIDTooLong",
 			network: models.Network{
 				NetID: "LongNetIDName",
 			},
 			errMessage: "Field validation for 'NetID' failed on the 'max' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "ListenPortTooLow",
 			network: models.Network{
 				DefaultListenPort: 1023,
 			},
 			errMessage: "Field validation for 'DefaultListenPort' failed on the 'min' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "ListenPortTooHigh",
 			network: models.Network{
 				DefaultListenPort: 65536,
 			},
 			errMessage: "Field validation for 'DefaultListenPort' failed on the 'max' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "KeepAliveTooBig",
 			network: models.Network{
 				DefaultKeepalive: 1010,
 			},
 			errMessage: "Field validation for 'DefaultKeepalive' failed on the 'max' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "InvalidLocalRange",
 			network: models.Network{
 				LocalRange: "192.168.0.1",
 			},
 			errMessage: "Field validation for 'LocalRange' failed on the 'cidr' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "CheckInIntervalTooBig",
 			network: models.Network{
 				DefaultCheckInInterval: 100001,
 			},
 			errMessage: "Field validation for 'DefaultCheckInInterval' failed on the 'max' tag",
 		},
-		NetworkValidationTestCase{
+		{
 			testname: "CheckInIntervalTooSmall",
 			network: models.Network{
 				DefaultCheckInInterval: 1,

+ 8 - 5
controllers/nodeGrpcController.go

@@ -12,10 +12,12 @@ import (
 	"github.com/gravitl/netmaker/models"
 )
 
+// NodeServiceServer - represents the service server for gRPC
 type NodeServiceServer struct {
 	nodepb.UnimplementedNodeServiceServer
 }
 
+// NodeServiceServer.ReadNode - reads node and responds with gRPC
 func (s *NodeServiceServer) ReadNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
 	// convert string id (from proto) to mongoDB ObjectId
 	macAndNetwork := strings.Split(req.Data, "###")
@@ -41,6 +43,7 @@ func (s *NodeServiceServer) ReadNode(ctx context.Context, req *nodepb.Object) (*
 	return response, nil
 }
 
+// NodeServiceServer.CreateNode - creates a node and responds over gRPC
 func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
 	// Get the protobuf node type from the protobuf request type
 	// Essentially doing req.Node to access the struct with a nil check
@@ -86,6 +89,7 @@ func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.Object)
 	return response, nil
 }
 
+// NodeServiceServer.UpdateNode updates a node and responds over gRPC
 func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
 	// Get the node data from the request
 	var newnode models.Node
@@ -113,6 +117,7 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.Object)
 	}, nil
 }
 
+// NodeServiceServer.DeleteNode - deletes a node and responds over gRPC
 func (s *NodeServiceServer) DeleteNode(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
 	nodeID := req.GetData()
 
@@ -127,6 +132,7 @@ func (s *NodeServiceServer) DeleteNode(ctx context.Context, req *nodepb.Object)
 	}, nil
 }
 
+// NodeServiceServer.GetPeers - fetches peers over gRPC
 func (s *NodeServiceServer) GetPeers(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
 	macAndNetwork := strings.Split(req.Data, "###")
 	if len(macAndNetwork) == 2 {
@@ -135,7 +141,7 @@ func (s *NodeServiceServer) GetPeers(ctx context.Context, req *nodepb.Object) (*
 		if err != nil {
 			return nil, err
 		}
-		if node.IsServer == "yes" && logic.IsLeader(&node){
+		if node.IsServer == "yes" && logic.IsLeader(&node) {
 			logic.SetNetworkServerPeers(&node)
 		}
 		excludeIsRelayed := node.IsRelay != "yes"
@@ -161,10 +167,7 @@ func (s *NodeServiceServer) GetPeers(ctx context.Context, req *nodepb.Object) (*
 	}, errors.New("could not fetch peers, invalid node id")
 }
 
-/**
- * Return Ext Peers (clients).NodeCheckIn
- * When a gateway node checks in, it pulls these peers to add to peers list in addition to normal network peers.
- */
+// NodeServiceServer.GetExtPeers - returns ext peers for a gateway node
 func (s *NodeServiceServer) GetExtPeers(ctx context.Context, req *nodepb.Object) (*nodepb.Object, error) {
 	// Initiate a NodeItem type to write decoded data to
 	//data := &models.PeersResponse{}

+ 3 - 2
controllers/nodeHttpController.go

@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"strings"
 	"time"
+
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/dnslogic"
@@ -71,7 +72,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
 			return
 		} else {
 
-			//Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API untill approved).
+			//Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API until approved).
 			collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
 			if err != nil {
 				errorResponse.Code = http.StatusBadRequest
@@ -189,7 +190,7 @@ func authorize(networkCheck bool, authNetwork string, next http.Handler) http.Ha
 			//This checks if
 			//A: the token is the master password
 			//B: the token corresponds to a mac address, and if so, which one
-			//TODO: There's probably a better way of dealing with the "master token"/master password. Plz Halp.
+			//TODO: There's probably a better way of dealing with the "master token"/master password. Plz Help.
 			var isAuthorized = false
 			var macaddress = ""
 			username, networks, isadmin, errN := functions.VerifyUserToken(authToken)

+ 5 - 0
controllers/relay.go

@@ -33,6 +33,7 @@ func createRelay(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(node)
 }
 
+// CreateRelay - creates a relay
 func CreateRelay(relay models.RelayRequest) (models.Node, error) {
 	node, err := functions.GetNodeByMacAddress(relay.NetID, relay.NodeID)
 	if node.OS == "windows" || node.OS == "macos" { // add in darwin later
@@ -87,6 +88,7 @@ func deleteRelay(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(node)
 }
 
+// SetRelayedNodes- set relayed nodes
 func SetRelayedNodes(yesOrno string, networkName string, addrs []string) error {
 
 	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -118,6 +120,7 @@ func SetRelayedNodes(yesOrno string, networkName string, addrs []string) error {
 	return nil
 }
 
+// ValidateRelay - checks if relay is valid
 func ValidateRelay(relay models.RelayRequest) error {
 	var err error
 	//isIp := functions.IsIpCIDR(gateway.RangeString)
@@ -128,6 +131,7 @@ func ValidateRelay(relay models.RelayRequest) error {
 	return err
 }
 
+// UpdateRelay - updates a relay
 func UpdateRelay(network string, oldAddrs []string, newAddrs []string) {
 	time.Sleep(time.Second / 4)
 	err := SetRelayedNodes("no", network, oldAddrs)
@@ -140,6 +144,7 @@ func UpdateRelay(network string, oldAddrs []string, newAddrs []string) {
 	}
 }
 
+// DeleteRelay - deletes a relay
 func DeleteRelay(network, macaddress string) (models.Node, error) {
 
 	node, err := functions.GetNodeByMacAddress(network, macaddress)

+ 1 - 1
controllers/serverHttpController.go

@@ -49,7 +49,7 @@ func securityCheckServer(adminonly bool, next http.Handler) http.HandlerFunc {
 		if !adminonly && (err != nil || user == "") {
 			returnErrorResponse(w, r, errorResponse)
 			return
-		} 
+		}
 		if adminonly && !isadmin && !authenticateMasterServer(authToken) {
 			returnErrorResponse(w, r, errorResponse)
 			return

+ 53 - 31
controllers/userHttpController.go

@@ -28,11 +28,11 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/users", authorizeUserAdm(http.HandlerFunc(getUsers))).Methods("GET")
 }
 
-//Node authenticates using its password and retrieves a JWT for authorization.
+// Node authenticates using its password and retrieves a JWT for authorization.
 func authenticateUser(response http.ResponseWriter, request *http.Request) {
 
-	//Auth request consists of Mac Address and Password (from node that is authorizing
-	//in case of Master, auth is ignored and mac is set to "mastermac"
+	// Auth request consists of Mac Address and Password (from node that is authorizing
+	// in case of Master, auth is ignored and mac is set to "mastermac"
 	var authRequest models.UserAuthParams
 	var errorResponse = models.ErrorResponse{
 		Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
@@ -53,7 +53,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 	}
 
 	if jwt == "" {
-		//very unlikely that err is !nil and no jwt returned, but handle it anyways.
+		// very unlikely that err is !nil and no jwt returned, but handle it anyways.
 		returnErrorResponse(response, request, formatError(errors.New("No token returned"), "internal"))
 		return
 	}
@@ -67,7 +67,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 			UserName:  username,
 		},
 	}
-	//Send back the JWT
+	// Send back the JWT
 	successJSONResponse, jsonError := json.Marshal(successResponse)
 
 	if jsonError != nil {
@@ -79,6 +79,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) {
 	response.Write(successJSONResponse)
 }
 
+// VerifyAuthRequest - verifies an auth request
 func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
 	var result models.User
 	if authRequest.UserName == "" {
@@ -86,7 +87,7 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
 	} else if authRequest.Password == "" {
 		return "", errors.New("password can't be empty")
 	}
-	//Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API untill approved).
+	//Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API until approved).
 	record, err := database.FetchRecord(database.USERS_TABLE_NAME, authRequest.UserName)
 	if err != nil {
 		return "", errors.New("incorrect credentials")
@@ -95,9 +96,9 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
 		return "", errors.New("incorrect credentials")
 	}
 
-	//compare password from request to stored password in database
-	//might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text...
-	//TODO: Consider a way of hashing the password client side before sending, or using certificates
+	// compare password from request to stored password in database
+	// might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text...
+	// TODO: Consider a way of hashing the password client side before sending, or using certificates
 	if err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(authRequest.Password)); err != nil {
 		return "", errors.New("incorrect credentials")
 	}
@@ -107,19 +108,19 @@ func VerifyAuthRequest(authRequest models.UserAuthParams) (string, error) {
 	return tokenString, nil
 }
 
-//The middleware for most requests to the API
-//They all pass  through here first
-//This will validate the JWT (or check for master token)
-//This will also check against the authNetwork and make sure the node should be accessing that endpoint,
-//even if it's technically ok
-//This is kind of a poor man's RBAC. There's probably a better/smarter way.
-//TODO: Consider better RBAC implementations
+// The middleware for most requests to the API
+// They all pass  through here first
+// This will validate the JWT (or check for master token)
+// This will also check against the authNetwork and make sure the node should be accessing that endpoint,
+// even if it's technically ok
+// This is kind of a poor man's RBAC. There's probably a better/smarter way.
+// TODO: Consider better RBAC implementations
 func authorizeUser(next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/json")
 		var params = mux.Vars(r)
 
-		//get the auth token
+		// get the auth token
 		bearerToken := r.Header.Get("Authorization")
 		username := params["username"]
 		err := ValidateUserToken(bearerToken, username, false)
@@ -150,6 +151,7 @@ func authorizeUserAdm(next http.Handler) http.HandlerFunc {
 	}
 }
 
+// ValidateUserToken - self explained
 func ValidateUserToken(token string, user string, adminonly bool) error {
 	var tokenSplit = strings.Split(token, " ")
 	//I put this in in case the user doesn't put in a token at all (in which case it's empty)
@@ -179,6 +181,7 @@ func ValidateUserToken(token string, user string, adminonly bool) error {
 	return nil
 }
 
+// HasAdmin - checks if server has an admin
 func HasAdmin() (bool, error) {
 
 	collection, err := database.FetchRecords(database.USERS_TABLE_NAME)
@@ -218,6 +221,7 @@ func hasAdmin(w http.ResponseWriter, r *http.Request) {
 
 }
 
+// GetUser - gets a user
 func GetUser(username string) (models.ReturnUser, error) {
 
 	var user models.ReturnUser
@@ -231,6 +235,7 @@ func GetUser(username string) (models.ReturnUser, error) {
 	return user, err
 }
 
+// GetUserInternal - gets an internal user
 func GetUserInternal(username string) (models.User, error) {
 
 	var user models.User
@@ -244,6 +249,7 @@ func GetUserInternal(username string) (models.User, error) {
 	return user, err
 }
 
+// GetUsers - gets users
 func GetUsers() ([]models.ReturnUser, error) {
 
 	var users []models.ReturnUser
@@ -267,7 +273,7 @@ func GetUsers() ([]models.ReturnUser, error) {
 	return users, err
 }
 
-//Get an individual node. Nothin fancy here folks.
+// Get an individual node. Nothin fancy here folks.
 func getUser(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
@@ -284,7 +290,7 @@ func getUser(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(user)
 }
 
-//Get an individual node. Nothin fancy here folks.
+// Get an individual node. Nothin fancy here folks.
 func getUsers(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
@@ -300,8 +306,9 @@ func getUsers(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(users)
 }
 
+// CreateUser - creates a user
 func CreateUser(user models.User) (models.User, error) {
-	//check if user exists
+	// check if user exists
 	if _, err := GetUser(user.UserName); err == nil {
 		return models.User{}, errors.New("user exists")
 	}
@@ -310,18 +317,18 @@ func CreateUser(user models.User) (models.User, error) {
 		return models.User{}, err
 	}
 
-	//encrypt that password so we never see it again
+	// encrypt that password so we never see it again
 	hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
 	if err != nil {
 		return user, err
 	}
-	//set password to encrypted password
+	// set password to encrypted password
 	user.Password = string(hash)
 
 	tokenString, _ := functions.CreateUserJWT(user.UserName, user.Networks, user.IsAdmin)
 
 	if tokenString == "" {
-		//returnErrorResponse(w, r, errorResponse)
+		// returnErrorResponse(w, r, errorResponse)
 		return user, err
 	}
 
@@ -339,10 +346,10 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
 	var admin models.User
-	//get node from body of request
+	// get node from body of request
 	_ = json.NewDecoder(r.Body).Decode(&admin)
-	admin.IsAdmin = true
-	admin, err := CreateUser(admin)
+
+	admin, err := CreateAdmin(admin)
 
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
@@ -352,11 +359,23 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(admin)
 }
 
+func CreateAdmin(admin models.User) (models.User, error) {
+	hasadmin, err := HasAdmin()
+	if err != nil {
+		return models.User{}, err
+	}
+	if hasadmin {
+		return models.User{}, errors.New("admin user already exists")
+	}
+	admin.IsAdmin = true
+	return CreateUser(admin)
+}
+
 func createUser(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
 	var user models.User
-	//get node from body of request
+	// get node from body of request
 	_ = json.NewDecoder(r.Body).Decode(&user)
 
 	user, err := CreateUser(user)
@@ -369,6 +388,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(user)
 }
 
+// UpdateUser - updates a given user
 func UpdateUser(userchange models.User, user models.User) (models.User, error) {
 	//check if user exists
 	if _, err := GetUser(user.UserName); err != nil {
@@ -389,13 +409,13 @@ func UpdateUser(userchange models.User, user models.User) (models.User, error) {
 		user.Networks = userchange.Networks
 	}
 	if userchange.Password != "" {
-		//encrypt that password so we never see it again
+		// encrypt that password so we never see it again
 		hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
 
 		if err != nil {
 			return userchange, err
 		}
-		//set password to encrypted password
+		// set password to encrypted password
 		userchange.Password = string(hash)
 
 		user.Password = userchange.Password
@@ -418,7 +438,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	var params = mux.Vars(r)
 	var user models.User
-	//start here
+	// start here
 	username := params["username"]
 	user, err := GetUserInternal(username)
 	if err != nil {
@@ -446,7 +466,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	var params = mux.Vars(r)
 	var user models.User
-	//start here
+	// start here
 	username := params["username"]
 	user, err := GetUserInternal(username)
 	if err != nil {
@@ -469,6 +489,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(user)
 }
 
+// DeleteUser - deletes a given user
 func DeleteUser(user string) (bool, error) {
 
 	if userRecord, err := database.FetchRecord(database.USERS_TABLE_NAME, user); err != nil || len(userRecord) == 0 {
@@ -504,6 +525,7 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(params["username"] + " deleted.")
 }
 
+// ValidateUser - validates a user model
 func ValidateUser(operation string, user models.User) error {
 
 	v := validator.New()

+ 69 - 1
controllers/userHttpController_test.go

@@ -71,6 +71,26 @@ func TestCreateUser(t *testing.T) {
 	})
 }
 
+func TestCreateAdmin(t *testing.T) {
+	database.InitializeDatabase()
+	deleteAllUsers()
+	var user models.User
+	t.Run("NoAdmin", func(t *testing.T) {
+		user.UserName = "admin"
+		user.Password = "password"
+		admin, err := CreateAdmin(user)
+		assert.Nil(t, err)
+		assert.Equal(t, user.UserName, admin.UserName)
+	})
+	t.Run("AdminExists", func(t *testing.T) {
+		user.UserName = "admin2"
+		user.Password = "password1"
+		admin, err := CreateAdmin(user)
+		assert.EqualError(t, err, "admin user already exists")
+		assert.Equal(t, admin, models.User{})
+	})
+}
+
 func TestDeleteUser(t *testing.T) {
 	database.InitializeDatabase()
 	deleteAllUsers()
@@ -153,6 +173,54 @@ func TestGetUser(t *testing.T) {
 	})
 }
 
+func TestGetUserInternal(t *testing.T) {
+	database.InitializeDatabase()
+	deleteAllUsers()
+	t.Run("NonExistantUser", func(t *testing.T) {
+		admin, err := GetUserInternal("admin")
+		assert.EqualError(t, err, "could not find any records")
+		assert.Equal(t, "", admin.UserName)
+	})
+	t.Run("UserExisits", func(t *testing.T) {
+		user := models.User{"admin", "password", nil, true}
+		CreateUser(user)
+		admin, err := GetUserInternal("admin")
+		assert.Nil(t, err)
+		assert.Equal(t, user.UserName, admin.UserName)
+	})
+}
+
+func TestGetUsers(t *testing.T) {
+	database.InitializeDatabase()
+	deleteAllUsers()
+	t.Run("NonExistantUser", func(t *testing.T) {
+		admin, err := GetUsers()
+		assert.EqualError(t, err, "could not find any records")
+		assert.Equal(t, []models.ReturnUser(nil), admin)
+	})
+	t.Run("UserExisits", func(t *testing.T) {
+		user := models.User{"admin", "password", nil, true}
+		CreateUser(user)
+		admins, err := GetUsers()
+		assert.Nil(t, err)
+		assert.Equal(t, user.UserName, admins[0].UserName)
+	})
+	t.Run("MulipleUsers", func(t *testing.T) {
+		user := models.User{"user", "password", nil, true}
+		CreateUser(user)
+		admins, err := GetUsers()
+		assert.Nil(t, err)
+		for _, u := range admins {
+			if u.UserName == "admin" {
+				assert.Equal(t, "admin", u.UserName)
+			} else {
+				assert.Equal(t, user.UserName, u.UserName)
+			}
+		}
+	})
+
+}
+
 func TestUpdateUser(t *testing.T) {
 	database.InitializeDatabase()
 	deleteAllUsers()
@@ -164,7 +232,7 @@ func TestUpdateUser(t *testing.T) {
 		assert.Equal(t, "", admin.UserName)
 	})
 
-	t.Run("UserExisits", func(t *testing.T) {
+	t.Run("UserExists", func(t *testing.T) {
 		CreateUser(user)
 		admin, err := UpdateUser(newuser, user)
 		assert.Nil(t, err)

+ 54 - 2
database/database.go

@@ -2,34 +2,75 @@ package database
 
 import (
 	"encoding/json"
-	"time"
 	"errors"
 	"log"
+	"time"
+
 	"github.com/gravitl/netmaker/servercfg"
 )
 
+// NETWORKS_TABLE_NAME - networks table
 const NETWORKS_TABLE_NAME = "networks"
+
+// NODES_TABLE_NAME - nodes table
 const NODES_TABLE_NAME = "nodes"
+
+// DELETED_NODES_TABLE_NAME - deleted nodes table
 const DELETED_NODES_TABLE_NAME = "deletednodes"
+
+// USERS_TABLE_NAME - users table
 const USERS_TABLE_NAME = "users"
+
+// DNS_TABLE_NAME - dns table
 const DNS_TABLE_NAME = "dns"
+
+// EXT_CLIENT_TABLE_NAME - ext client table
 const EXT_CLIENT_TABLE_NAME = "extclients"
+
+// INT_CLIENTS_TABLE_NAME - int client table
 const INT_CLIENTS_TABLE_NAME = "intclients"
+
+// PEERS_TABLE_NAME - peers table
 const PEERS_TABLE_NAME = "peers"
+
+// SERVERCONF_TABLE_NAME
+const SERVERCONF_TABLE_NAME = "serverconf"
+
+// DATABASE_FILENAME - database file name
 const DATABASE_FILENAME = "netmaker.db"
 
 // == ERROR CONSTS ==
+
+// NO_RECORD - no singular result found
 const NO_RECORD = "no result found"
+
+// NO_RECORDS - no results found
 const NO_RECORDS = "could not find any records"
 
 // == Constants ==
+
+// INIT_DB - initialize db
 const INIT_DB = "init"
+
+// CREATE_TABLE - create table const
 const CREATE_TABLE = "createtable"
+
+// INSERT - insert into db const
 const INSERT = "insert"
+
+// INSERT_PEER - insert peer into db const
 const INSERT_PEER = "insertpeer"
+
+// DELETE - delete db record const
 const DELETE = "delete"
+
+// DELETE_ALL - delete a table const
 const DELETE_ALL = "deleteall"
+
+// FETCH_ALL - fetch table contents const
 const FETCH_ALL = "fetchall"
+
+// CLOSE_DB - graceful close of db const
 const CLOSE_DB = "closedb"
 
 func getCurrentDB() map[string]interface{} {
@@ -38,13 +79,15 @@ func getCurrentDB() map[string]interface{} {
 		return RQLITE_FUNCTIONS
 	case "sqlite":
 		return SQLITE_FUNCTIONS
+	case "postgres":
+		return PG_FUNCTIONS
 	default:
 		return SQLITE_FUNCTIONS
 	}
 }
 
 func InitializeDatabase() error {
-	log.Println("connecting to",servercfg.GetDB())
+	log.Println("connecting to", servercfg.GetDB())
 	tperiod := time.Now().Add(10 * time.Second)
 	for {
 		if err := getCurrentDB()[INIT_DB].(func() error)(); err != nil {
@@ -70,17 +113,20 @@ func createTables() {
 	createTable(EXT_CLIENT_TABLE_NAME)
 	createTable(INT_CLIENTS_TABLE_NAME)
 	createTable(PEERS_TABLE_NAME)
+	createTable(SERVERCONF_TABLE_NAME)
 }
 
 func createTable(tableName string) error {
 	return getCurrentDB()[CREATE_TABLE].(func(string) error)(tableName)
 }
 
+// IsJSONString - checks if valid json
 func IsJSONString(value string) bool {
 	var jsonInt interface{}
 	return json.Unmarshal([]byte(value), &jsonInt) == nil
 }
 
+// Insert - inserts object into db
 func Insert(key string, value string, tableName string) error {
 	if key != "" && value != "" && IsJSONString(value) {
 		return getCurrentDB()[INSERT].(func(string, string, string) error)(key, value, tableName)
@@ -89,6 +135,7 @@ func Insert(key string, value string, tableName string) error {
 	}
 }
 
+// InsertPeer - inserts peer into db
 func InsertPeer(key string, value string) error {
 	if key != "" && value != "" && IsJSONString(value) {
 		return getCurrentDB()[INSERT_PEER].(func(string, string) error)(key, value)
@@ -97,10 +144,12 @@ func InsertPeer(key string, value string) error {
 	}
 }
 
+// DeleteRecord - deletes a record from db
 func DeleteRecord(tableName string, key string) error {
 	return getCurrentDB()[DELETE].(func(string, string) error)(tableName, key)
 }
 
+// DeleteAllRecords - removes a table and remakes
 func DeleteAllRecords(tableName string) error {
 	err := getCurrentDB()[DELETE_ALL].(func(string) error)(tableName)
 	if err != nil {
@@ -113,6 +162,7 @@ func DeleteAllRecords(tableName string) error {
 	return nil
 }
 
+// FetchRecord - fetches a record
 func FetchRecord(tableName string, key string) (string, error) {
 	results, err := FetchRecords(tableName)
 	if err != nil {
@@ -124,10 +174,12 @@ func FetchRecord(tableName string, key string) (string, error) {
 	return results[key], nil
 }
 
+// FetchRecords - fetches all records in given table
 func FetchRecords(tableName string) (map[string]string, error) {
 	return getCurrentDB()[FETCH_ALL].(func(string) (map[string]string, error))(tableName)
 }
 
+// CloseDB - closes a database gracefully
 func CloseDB() {
 	getCurrentDB()[CLOSE_DB].(func())()
 }

+ 132 - 0
database/postgres.go

@@ -0,0 +1,132 @@
+package database
+
+import (
+	"database/sql"
+	"errors"
+	"fmt"
+	"github.com/gravitl/netmaker/servercfg"
+	_ "github.com/lib/pq"
+)
+
+// PGDB - database object for PostGreSQL
+var PGDB *sql.DB
+
+// PG_FUNCTIONS - map of db functions for PostGreSQL
+var PG_FUNCTIONS = map[string]interface{}{
+	INIT_DB:      initPGDB,
+	CREATE_TABLE: pgCreateTable,
+	INSERT:       pgInsert,
+	INSERT_PEER:  pgInsertPeer,
+	DELETE:       pgDeleteRecord,
+	DELETE_ALL:   pgDeleteAllRecords,
+	FETCH_ALL:    pgFetchRecords,
+	CLOSE_DB:     pgCloseDB,
+}
+
+func getPGConnString() string {
+	pgconf := servercfg.GetSQLConf()
+	pgConn := fmt.Sprintf("host=%s port=%d user=%s "+
+		"password=%s dbname=%s sslmode=%s connect_timeout=5",
+		pgconf.Host, pgconf.Port, pgconf.Username, pgconf.Password, pgconf.DB, pgconf.SSLMode)
+	return pgConn
+}
+
+func initPGDB() error {
+	connString := getPGConnString()
+	var dbOpenErr error
+	PGDB, dbOpenErr = sql.Open("postgres", connString)
+	if dbOpenErr != nil {
+		return dbOpenErr
+	}
+	dbOpenErr = PGDB.Ping()
+
+	return dbOpenErr
+}
+
+func pgCreateTable(tableName string) error {
+	statement, err := PGDB.Prepare("CREATE TABLE IF NOT EXISTS " + tableName + " (key TEXT NOT NULL UNIQUE PRIMARY KEY, value TEXT)")
+	if err != nil {
+		return err
+	}
+	_, err = statement.Exec()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func pgInsert(key string, value string, tableName string) error {
+	if key != "" && value != "" && IsJSONString(value) {
+		insertSQL := "INSERT INTO " + tableName + " (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $3;"
+		statement, err := PGDB.Prepare(insertSQL)
+		if err != nil {
+			return err
+		}
+		_, err = statement.Exec(key, value, value)
+		if err != nil {
+			return err
+		}
+		return nil
+	} else {
+		return errors.New("invalid insert " + key + " : " + value)
+	}
+}
+
+func pgInsertPeer(key string, value string) error {
+	if key != "" && value != "" && IsJSONString(value) {
+		err := pgInsert(key, value, PEERS_TABLE_NAME)
+		if err != nil {
+			return err
+		}
+		return nil
+	} else {
+		return errors.New("invalid peer insert " + key + " : " + value)
+	}
+}
+
+func pgDeleteRecord(tableName string, key string) error {
+	deleteSQL := "DELETE FROM " + tableName + " WHERE key = $1;"
+	statement, err := PGDB.Prepare(deleteSQL)
+	if err != nil {
+		return err
+	}
+	if _, err = statement.Exec(key); err != nil {
+		return err
+	}
+	return nil
+}
+
+func pgDeleteAllRecords(tableName string) error {
+	deleteSQL := "DELETE FROM " + tableName
+	statement, err := PGDB.Prepare(deleteSQL)
+	if err != nil {
+		return err
+	}
+	if _, err = statement.Exec(); err != nil {
+		return err
+	}
+	return nil
+}
+
+func pgFetchRecords(tableName string) (map[string]string, error) {
+	row, err := PGDB.Query("SELECT * FROM " + tableName + " ORDER BY key")
+	if err != nil {
+		return nil, err
+	}
+	records := make(map[string]string)
+	defer row.Close()
+	for row.Next() { // Iterate and fetch the records from result cursor
+		var key string
+		var value string
+		row.Scan(&key, &value)
+		records[key] = value
+	}
+	if len(records) == 0 {
+		return nil, errors.New(NO_RECORDS)
+	}
+	return records, nil
+}
+
+func pgCloseDB() {
+	PGDB.Close()
+}

+ 4 - 4
database/rqlite.go

@@ -7,8 +7,10 @@ import (
 	"github.com/rqlite/gorqlite"
 )
 
+// RQliteDatabase - the rqlite db connection
 var RQliteDatabase gorqlite.Connection
 
+// RQLITE_FUNCTIONS - all the functions to run with rqlite
 var RQLITE_FUNCTIONS = map[string]interface{}{
 	INIT_DB:      initRqliteDatabase,
 	CREATE_TABLE: rqliteCreateTable,
@@ -46,9 +48,8 @@ func rqliteInsert(key string, value string, tableName string) error {
 			return err
 		}
 		return nil
-	} else {
-		return errors.New("invalid insert " + key + " : " + value)
 	}
+	return errors.New("invalid insert " + key + " : " + value)
 }
 
 func rqliteInsertPeer(key string, value string) error {
@@ -58,9 +59,8 @@ func rqliteInsertPeer(key string, value string) error {
 			return err
 		}
 		return nil
-	} else {
-		return errors.New("invalid peer insert " + key + " : " + value)
 	}
+	return errors.New("invalid peer insert " + key + " : " + value)
 }
 
 func rqliteDeleteRecord(tableName string, key string) error {

+ 6 - 6
database/sqlite.go

@@ -6,14 +6,16 @@ import (
 	"os"
 	"path/filepath"
 
-	_ "github.com/mattn/go-sqlite3"
+	_ "github.com/mattn/go-sqlite3" // need to blank import this package
 )
 
 // == sqlite ==
 const dbFilename = "netmaker.db"
 
+// SqliteDB is the db object fro sqlite database connections
 var SqliteDB *sql.DB
 
+// SQLITE_FUNCTIONS - contains a map of the functions for sqlite
 var SQLITE_FUNCTIONS = map[string]interface{}{
 	INIT_DB:      initSqliteDB,
 	CREATE_TABLE: sqliteCreateTable,
@@ -28,7 +30,7 @@ var SQLITE_FUNCTIONS = map[string]interface{}{
 func initSqliteDB() error {
 	// == create db file if not present ==
 	if _, err := os.Stat("data"); os.IsNotExist(err) {
-		os.Mkdir("data", 0644)
+		os.Mkdir("data", 0744)
 	}
 	dbFilePath := filepath.Join("data", dbFilename)
 	if _, err := os.Stat(dbFilePath); os.IsNotExist(err) {
@@ -67,9 +69,8 @@ func sqliteInsert(key string, value string, tableName string) error {
 			return err
 		}
 		return nil
-	} else {
-		return errors.New("invalid insert " + key + " : " + value)
 	}
+	return errors.New("invalid insert " + key + " : " + value)
 }
 
 func sqliteInsertPeer(key string, value string) error {
@@ -79,9 +80,8 @@ func sqliteInsertPeer(key string, value string) error {
 			return err
 		}
 		return nil
-	} else {
-		return errors.New("invalid peer insert " + key + " : " + value)
 	}
+	return errors.New("invalid peer insert " + key + " : " + value)
 }
 
 func sqliteDeleteRecord(tableName string, key string) error {

+ 5 - 0
database/statics.go

@@ -5,6 +5,7 @@ import (
 	"strings"
 )
 
+// SetPeers - sets peers for a network
 func SetPeers(newPeers map[string]string, networkName string) bool {
 	areEqual := PeersAreEqual(newPeers, networkName)
 	if !areEqual {
@@ -17,6 +18,8 @@ func SetPeers(newPeers map[string]string, networkName string) bool {
 	}
 	return !areEqual
 }
+
+// GetPeers - gets peers for a given network
 func GetPeers(networkName string) (map[string]string, error) {
 	record, err := FetchRecord(PEERS_TABLE_NAME, networkName)
 	if err != nil && !IsEmptyRecord(err) {
@@ -30,6 +33,7 @@ func GetPeers(networkName string) (map[string]string, error) {
 	return currentDataMap, err
 }
 
+// PeersAreEqual - checks if peers are the same
 func PeersAreEqual(toCompare map[string]string, networkName string) bool {
 	currentDataMap, err := GetPeers(networkName)
 	if err != nil {
@@ -46,6 +50,7 @@ func PeersAreEqual(toCompare map[string]string, networkName string) bool {
 	return true
 }
 
+// IsEmptyRecord - checks for if it's an empty record error or not
 func IsEmptyRecord(err error) bool {
 	if err == nil {
 		return false

+ 4 - 0
dnslogic/dns.go

@@ -10,6 +10,7 @@ import (
 	"github.com/txn2/txeh"
 )
 
+// SetDNS - sets the dns on file
 func SetDNS() error {
 	hostfile := txeh.Hosts{}
 	var corefilestring string
@@ -42,6 +43,7 @@ func SetDNS() error {
 	return err
 }
 
+// GetDNS - gets the DNS of a current network
 func GetDNS(network string) ([]models.DNSEntry, error) {
 
 	var dns []models.DNSEntry
@@ -58,6 +60,7 @@ func GetDNS(network string) ([]models.DNSEntry, error) {
 	return dns, nil
 }
 
+// GetNodeDNS - gets the DNS of a network node
 func GetNodeDNS(network string) ([]models.DNSEntry, error) {
 
 	var dns []models.DNSEntry
@@ -81,6 +84,7 @@ func GetNodeDNS(network string) ([]models.DNSEntry, error) {
 	return dns, nil
 }
 
+// GetCustomDNS - gets the custom DNS of a network
 func GetCustomDNS(network string) ([]models.DNSEntry, error) {
 
 	var dns []models.DNSEntry

+ 26 - 0
docker/Dockerfile-userspace

@@ -0,0 +1,26 @@
+ARG NM_VERSION=
+
+FROM gravitl/builder as builder
+
+RUN apk add --update git build-base libmnl-dev iptables
+
+WORKDIR /root/
+RUN git clone https://git.zx2c4.com/wireguard-go && \
+    cd wireguard-go && \
+    make && \
+    make install
+
+ENV WITH_WGQUICK=yes
+RUN git clone https://git.zx2c4.com/wireguard-tools && \
+    cd wireguard-tools && \
+    cd src && \
+    make && \
+    make install
+
+FROM gravitl/netmaker:${NM_VERSION}
+
+RUN apk add --no-cache --update bash libmnl iptables openresolv iproute2
+COPY --from=builder /usr/bin/wireguard-go /usr/bin/wg* /usr/bin/
+COPY scripts/userspace-entrypoint.sh ./entrypoint.sh
+
+ENTRYPOINT ["/bin/sh", "./entrypoint.sh"]

BIN
docs/_build/doctrees/architecture.doctree


BIN
docs/_build/doctrees/environment.pickle


BIN
docs/_build/doctrees/server-installation.doctree


+ 1 - 1
docs/_build/html/.buildinfo

@@ -1,4 +1,4 @@
 # Sphinx build info version 1
 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
-config: c5f7c337a81aafa96c2a1d23d89efa34
+config: c42328d03e8a69bc128d18c18d0c8114
 tags: 645f666f9bcd5a90fca523b33c5a78b7

+ 5 - 5
docs/_build/html/_sources/architecture.rst.txt

@@ -77,13 +77,13 @@ Netmaker Server
 
 The Netmaker server is, at its core, a golang binary. Source code can be found `on GitHub <https://github.com/gravitl/netmaker>`_. The binary, by itself can be compiled for most systems. If you need to run the Netmaker server on a particular system, it likely can be made to work. In typical deployments, it is run as a Docker container. It can also be run as a systemd service as outlined in the non-docker install guide.
 
-The Netmaker server acts as an API to the front end, and as a GRPC server to the machines in the network. GRPC is much faster and more efficient than standard API calls, which increases the speed of transactions. For this reason, the Netmaker server exposes two ports: The default for the API is 8081, and the default for GRPC is 50051. Either the API or the GRPC server can be disabled on any given Netmaker instance can be disabled, allowing you to deploy two different servers for managing the API (which is largely for the admin's use) and GRPC (which is largely for the nodes' use).
+The Netmaker server acts as an API to the front end, and as a GRPC server to the machines in the network. GRPC is much faster and more efficient than standard API calls, which increases the speed of transactions. For this reason, the Netmaker server exposes two ports: The default for the API is 8081, and the default for GRPC is 50051. Either the API or the GRPC server can be disabled on any given Netmaker instance, allowing you to deploy two different servers for managing the API (which is largely for the admin's use) and GRPC (which is largely for the nodes' use).
 
 Most server settings are configurable via a config file, or by environment variables (which take precedence). If the server finds neither of these, it sets sensible defaults, including things like the server's reachable IP, ports, and which "modes" to run in.
 
 These modes include client mode and dns mode. Either of these can be disabled but are enabled by default. Client mode allows you to treat the Netmaker host machine (operating system) as a network Node, installing the netclient and controlling the host network. DNS mode has the server write config settings for CoreDNS, a separate component and nameserver, which picks up the config settings to manage node DNS.
 
-The Netmaker server interacts with either sqlit (default) or rqlite, a distributed version of sqlite, as its database. This DB holds information about nodes, networks, users, and other important data. This data is configuration data. For the most part, Netmaker serves configuration data to Nodes, telling them how they should configure themselves. The Netclient is the agent that actually does that configuration.
+The Netmaker server interacts with either sqlite (default), postgres, or rqlite, a distributed version of sqlite, as its database. This DB holds information about nodes, networks, users, and other important data. This data is configuration data. For the most part, Netmaker serves configuration data to Nodes, telling them how they should configure themselves. The Netclient is the agent that actually does that configuration.
 
 
 Netclient
@@ -104,10 +104,10 @@ If running in daemon mode, on a periodic basis (systemd timer), the netclient pe
 The check in process is what allows Netmaker to create dynamic mesh networks. As nodes are added to, removed from, and modified on the network, other nodes are notified, and make appropriate changes.
 
 
-sqlite and rqlite
----------------------
+Datavase (sqlite, rqlite, postgres)
+-------------------------------------
 
-As of v0.8, Netmaker uses sqlite by default as a database. It can also use rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users. 
+As of v0.8, Netmaker uses sqlite by default as a database. It can also use PostgreSQL, or rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users. 
 
 Additional database support (besides sqlite and rqlite) is very easy to implement for special use cases. Netmaker uses simple key value lookups to run the networks, and the database was designed to be extensible, so support for key-value stores and other SQL-based databases can be achieved by changing a single file.
 

+ 162 - 10
docs/_build/html/_sources/server-installation.rst.txt

@@ -9,7 +9,7 @@ System Compatibility
 
 Netmaker will require elevated privileges to perform network operations. Netmaker has similar limitations to :doc:`netclient <./client-installation>` (client networking agent). 
 
-Typically, Netmaker is run inside of containers (Docker). To run a non-docker installation, you must run the Netmaker binary, CoreDNS binary, rqlite, and a web server directly on the host. Each of these components have their own individual requirements.
+Typically, Netmaker is run inside of containers (Docker). To run a non-docker installation, you must run the Netmaker binary, CoreDNS binary, database, and a web server directly on the host. Each of these components have their own individual requirements.
 
 The quick install guide is recommended for first-time installs. 
 
@@ -101,13 +101,38 @@ DNS_MODE:
 DATABASE:  
     **Default:** "sqlite"
 
-    **Description:** Specify db type to connect with. Currently, options include "sqlite" and "rqlite".
+    **Description:** Specify db type to connect with. Currently, options include "sqlite", "rqlite", and "postgres".
 
-SQL_CONN:  
+SQL_CONN:
     **Default:** "http://"
 
     **Description:** Specify the necessary string to connect with your local or remote sql database.
 
+SQL_HOST:
+    **Default:** "localhost"
+
+    **Description:** Host where postgres is running.
+
+SQL_PORT:
+    **Default:** "5432"
+
+    **Description:** port postgres is running.
+
+SQL_DB:
+    **Default:** "netmaker"
+
+    **Description:** DB to use in postgres.
+
+SQL_USER:
+    **Default:** "postgres"
+
+    **Description:** User for posgres.
+
+SQL_PASS:
+    **Default:** "nopass"
+
+    **Description:** Password for postgres.
+
 CLIENT_MODE:  
     **Default:** "on"
 
@@ -344,18 +369,135 @@ The following file configures Netmaker as a subdomain. This config is an adaptio
 
 .. _HAInstall:
 
-Highly Available Installation
-===============================
+
+
+Highly Available Installation (Kubernetes)
+==================================================
+
+Netmaker comes with a Helm chart to deploy with High Availability on Kubernetes:
+
+.. code-block::
+
+    helm repo add netmaker https://gravitl.github.io/netmaker-helm/
+    helm repo update
+
+Requirements
+---------------
+
+To run HA Netmaker on Kubernetes, your cluster must have the following:
+- RWO and RWX Storage Classes (RWX is only required if running Netmaker with DNS Management enabled).
+- An Ingress Controller and valid TLS certificates 
+- This chart can currently generate ingress for Nginx or Traefik Ingress with LetsEncrypt + Cert Manager
+- If LetsEncrypt and CertManager are not deployed, you must manually configure certificates for your ingress
+
+Furthermore, the chart will by default install and use a postgresql cluster as its datastore.
+
+Recommended Settings:
+----------------------
+A minimal HA install of Netmaker can be run with the following command:
+`helm install netmaker --generate-name --set baseDomain=nm.example.com`
+This install has some notable exceptions:
+- Ingress **must** be manually configured post-install (need to create valid Ingress with TLS)
+- Server will use "userspace" WireGuard, which is slower than kernel WG
+- DNS will be disabled
+
+Example Installations:
+------------------------
+An annotated install command:
+
+.. code-block::
+
+    helm install netmaker/netmaker --generate-name \ # generate a random id for the deploy 
+    --set baseDomain=nm.example.com \ # the base wildcard domain to use for the netmaker api/dashboard/grpc ingress 
+    --set replicas=3 \ # number of server replicas to deploy (3 by default) 
+    --set ingress.enabled=true \ # deploy ingress automatically (requires nginx or traefik and cert-manager + letsencrypt) 
+    --set ingress.className=nginx \ # ingress class to use 
+    --set ingress.tls.issuerName=letsencrypt-prod \ # LetsEncrypt certificate issuer to use 
+    --set dns.enabled=true \ # deploy and enable private DNS management with CoreDNS 
+    --set dns.clusterIP=10.245.75.75 --set dns.RWX.storageClassName=nfs \ # required fields for DNS 
+    --set postgresql-ha.postgresql.replicaCount=2 \ # number of DB replicas to deploy (default 2)
+
+
+The below command will install netmaker with two server replicas, a coredns server, and ingress with routes of api.nm.example.com, grpc.nm.example.com, and dashboard.nm.example.com. CoreDNS will be reachable at 10.245.75.75, and will use NFS to share a volume with Netmaker (to configure dns entries).
+
+.. code-block::
+
+    helm install netmaker/netmaker --generate-name --set baseDomain=nm.example.com \
+    --set replicas=2 --set ingress.enabled=true --set dns.enabled=true \
+    --set dns.clusterIP=10.245.75.75 --set dns.RWX.storageClassName=nfs \
+    --set ingress.className=nginx
+
+The below command will install netmaker with three server replicas (the default), **no coredns**, and ingress with routes of api.netmaker.example.com, grpc.netmaker.example.com, and dashboard.netmaker.example.com. There will be one UI replica instead of two, and one database instance instead of two. Traefik will look for a ClusterIssuer named "le-prod-2" to get valid certificates for the ingress. 
+
+.. code-block::
+
+    helm3 install netmaker/netmaker --generate-name \
+    --set baseDomain=netmaker.example.com --set postgresql-ha.postgresql.replicaCount=1 \
+    --set ui.replicas=1 --set ingress.enabled=true \
+    --set ingress.tls.issuerName=le-prod-2 --set ingress.className=traefik
+
+Below, we discuss the considerations for Ingress, Kernel WireGuard, and DNS.
+
+Ingress	
+----------
+To run HA Netmaker, you must have ingress installed and enabled on your cluster with valid TLS certificates (not self-signed). If you are running Nginx as your Ingress Controller and LetsEncrypt for TLS certificate management, you can run the helm install with the following settings:
+
+- `--set ingress.enabled=true`
+- `--set ingress.annotations.cert-manager.io/cluster-issuer=<your LE issuer name>`
+
+If you are not using Nginx or Traefik and LetsEncrypt, we recommend leaving ingress.enabled=false (default), and then manually creating the ingress objects post-install. You will need three ingress objects with TLS:
+
+- `dashboard.<baseDomain>`
+- `api.<baseDomain>`
+- `grpc.<baseDomain>`
+
+If deploying manually, the gRPC ingress object requires special considerations. Look up the proper way to route grpc with your ingress controller. For instance, on Traefik, an IngressRouteTCP object is required.
+
+There are some example ingress objects in the kube/example folder.
+
+Kernel WireGuard
+------------------
+If you have control of the Kubernetes worker node servers, we recommend **first** installing WireGuard on the hosts, and then installing HA Netmaker in Kernel mode. By default, Netmaker will install with userspace WireGuard (wireguard-go) for maximum compatibility, and to avoid needing permissions at the host level. If you have installed WireGuard on your hosts, you should install Netmaker's helm chart with the following option:
+
+- `--set wireguard.kernel=true`
+
+DNS
+----------
+By Default, the helm chart will deploy without DNS enabled. To enable DNS, specify with:
+
+- `--set dns.enabled=true` 
+
+This will require specifying a RWX storage class, e.g.:
+
+- `--set dns.RWX.storageClassName=nfs`
+
+This will also require specifying a service address for DNS. Choose a valid ipv4 address from the service IP CIDR for your cluster, e.g.:
+
+- `--set dns.clusterIP=10.245.69.69`
+
+**This address will only be reachable from hosts that have access to the cluster service CIDR.** It is only designed for use cases related to k8s. If you want a more general-use Netmaker server on Kubernetes for use cases outside of k8s, you will need to do one of the following:
+- bind the CoreDNS service to port 53 on one of your worker nodes and set the COREDNS_ADDRESS equal to the public IP of the worker node
+- Create a private Network with Netmaker and set the COREDNS_ADDRESS equal to the private address of the host running CoreDNS. For this, CoreDNS will need a node selector and will ideally run on the same host as one of the Netmaker server instances.
+
+Values
+---------
+
+To view all options for the chart, please visit the README in the code repo `here <https://github.com/gravitl/netmaker/tree/master/kube/helm#values>`_ .
+
+Highly Available Installation (VMs/Bare Metal)
+==================================================
 
 For an enterprise Netmaker installation, you will need a server that is highly available, to ensure redundant WireGuard routing when any server goes down. To do this, you will need:
 
 1. A load balancer
 2. 3+ Netmaker server instances
-3. rqlite as the backing database
+3. rqlite or PostgreSQL as the backing database
 
 These documents outline general HA installation guidelines. Netmaker is highly customizable to meet a wide range of enterprise environments. If you would like support with an enterprise-grade Netmaker installation, you can `schedule a consultation here <https://gravitl.com/book>`_ . 
 
-The main consideration here is how to configure rqlite. Most other settings and procedures match the standardized way of making applications HA: Load balancing to multiple instances, and sharing a DB. In our case, the DB (rqlite) is distributed, making HA data more easily achievable.
+The main consideration for this document is how to configure rqlite. Most other settings and procedures match the standardized way of making applications HA: Load balancing to multiple instances, and sharing a DB. In our case, the DB (rqlite) is distributed, making HA data more easily achievable.
+
+If using PostgreSQL, follow their documentation for `installing in HA mode <https://www.postgresql.org/docs/14/high-availability.html>`_ and skip step #2.
 
 1. Load Balancer Setup
 ------------------------
@@ -398,7 +540,19 @@ Once rqlite instances have been configured, the Netmaker servers can be deployed
 3. Netmaker Setup
 ------------------
 
-Netmaker will be started on each node with default settings, except with DATABASE=rqlite and SQL_CONN set appropriately to reach the local rqlite instance. Rqlite will maintain consistency with each Netmaker backend.
+Netmaker will be started on each node with default settings, except with DATABASE=rqlite (or DATABASE=postgress) and SQL_CONN set appropriately to reach the local rqlite instance. Rqlite will maintain consistency with each Netmaker backend.
+
+If deploying HA with PostgreSQL, you will connect with the following settings:
+
+.. code-block::
+
+    SQL_HOST = <sql host>
+    SQL_PORT = <port>
+    SQL_DB   = <designated sql DB>
+    SQL_USER = <your user>
+    SQL_PASS = <your password>
+    DATABASE = postgres
+
 
 4. Other Considerations
 ------------------------
@@ -408,5 +562,3 @@ This is enough to get a functioning HA installation of Netmaker. However, you ma
 
 
 
-
-

+ 1 - 1
docs/_build/html/_static/documentation_options.js

@@ -1,6 +1,6 @@
 var DOCUMENTATION_OPTIONS = {
     URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
-    VERSION: '0.8.2',
+    VERSION: '0.8.4',
     LANGUAGE: 'None',
     COLLAPSE_INDEX: false,
     BUILDER: 'html',

+ 26 - 5
docs/_build/html/about.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>About &#8212; Netmaker 0.8.2 documentation</title>
+    <title>About &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -295,6 +295,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -482,6 +489,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 26 - 5
docs/_build/html/api.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>API Reference &#8212; Netmaker 0.8.2 documentation</title>
+    <title>API Reference &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -462,6 +469,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 35 - 14
docs/_build/html/architecture.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Architecture &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Architecture &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -56,7 +56,7 @@
     <link rel="author" title="About these documents" href="about.html" />
     <link rel="index" title="Index" href="genindex.html" />
     <link rel="search" title="Search" href="search.html" />
-    <link rel="next" title="Quick Install" href="quick-start.html" />
+    <link rel="next" title="Install" href="install.html" />
     <link rel="prev" title="About" href="about.html" />
   
    
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -268,7 +268,7 @@
         </li>
         <li class="md-nav__item"><a href="#netclient" class="md-nav__link">Netclient</a>
         </li>
-        <li class="md-nav__item"><a href="#sqlite-and-rqlite" class="md-nav__link">sqlite and rqlite</a>
+        <li class="md-nav__item"><a href="#datavase-sqlite-rqlite-postgres" class="md-nav__link">Datavase (sqlite, rqlite, postgres)</a>
         </li>
         <li class="md-nav__item"><a href="#netmaker-ui" class="md-nav__link">Netmaker UI</a>
         </li>
@@ -325,6 +325,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -512,6 +519,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>
@@ -810,7 +831,7 @@
         </li>
         <li class="md-nav__item"><a href="#netclient" class="md-nav__link">Netclient</a>
         </li>
-        <li class="md-nav__item"><a href="#sqlite-and-rqlite" class="md-nav__link">sqlite and rqlite</a>
+        <li class="md-nav__item"><a href="#datavase-sqlite-rqlite-postgres" class="md-nav__link">Datavase (sqlite, rqlite, postgres)</a>
         </li>
         <li class="md-nav__item"><a href="#netmaker-ui" class="md-nav__link">Netmaker UI</a>
         </li>
@@ -882,10 +903,10 @@
 
 <h3 id="netmaker-server">Netmaker Server<a class="headerlink" href="#netmaker-server" title="Permalink to this headline">¶</a></h3>
 <p>The Netmaker server is, at its core, a golang binary. Source code can be found <a class="reference external" href="https://github.com/gravitl/netmaker">on GitHub</a>. The binary, by itself can be compiled for most systems. If you need to run the Netmaker server on a particular system, it likely can be made to work. In typical deployments, it is run as a Docker container. It can also be run as a systemd service as outlined in the non-docker install guide.</p>
-<p>The Netmaker server acts as an API to the front end, and as a GRPC server to the machines in the network. GRPC is much faster and more efficient than standard API calls, which increases the speed of transactions. For this reason, the Netmaker server exposes two ports: The default for the API is 8081, and the default for GRPC is 50051. Either the API or the GRPC server can be disabled on any given Netmaker instance can be disabled, allowing you to deploy two different servers for managing the API (which is largely for the admin’s use) and GRPC (which is largely for the nodes’ use).</p>
+<p>The Netmaker server acts as an API to the front end, and as a GRPC server to the machines in the network. GRPC is much faster and more efficient than standard API calls, which increases the speed of transactions. For this reason, the Netmaker server exposes two ports: The default for the API is 8081, and the default for GRPC is 50051. Either the API or the GRPC server can be disabled on any given Netmaker instance, allowing you to deploy two different servers for managing the API (which is largely for the admin’s use) and GRPC (which is largely for the nodes’ use).</p>
 <p>Most server settings are configurable via a config file, or by environment variables (which take precedence). If the server finds neither of these, it sets sensible defaults, including things like the server’s reachable IP, ports, and which “modes” to run in.</p>
 <p>These modes include client mode and dns mode. Either of these can be disabled but are enabled by default. Client mode allows you to treat the Netmaker host machine (operating system) as a network Node, installing the netclient and controlling the host network. DNS mode has the server write config settings for CoreDNS, a separate component and nameserver, which picks up the config settings to manage node DNS.</p>
-<p>The Netmaker server interacts with either sqlit (default) or rqlite, a distributed version of sqlite, as its database. This DB holds information about nodes, networks, users, and other important data. This data is configuration data. For the most part, Netmaker serves configuration data to Nodes, telling them how they should configure themselves. The Netclient is the agent that actually does that configuration.</p>
+<p>The Netmaker server interacts with either sqlite (default), postgres, or rqlite, a distributed version of sqlite, as its database. This DB holds information about nodes, networks, users, and other important data. This data is configuration data. For the most part, Netmaker serves configuration data to Nodes, telling them how they should configure themselves. The Netclient is the agent that actually does that configuration.</p>
 
 
 <h3 id="netclient">Netclient<a class="headerlink" href="#netclient" title="Permalink to this headline">¶</a></h3>
@@ -898,8 +919,8 @@
 <p>The check in process is what allows Netmaker to create dynamic mesh networks. As nodes are added to, removed from, and modified on the network, other nodes are notified, and make appropriate changes.</p>
 
 
-<h3 id="sqlite-and-rqlite">sqlite and rqlite<a class="headerlink" href="#sqlite-and-rqlite" title="Permalink to this headline">¶</a></h3>
-<p>As of v0.8, Netmaker uses sqlite by default as a database. It can also use rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users.</p>
+<h3 id="datavase-sqlite-rqlite-postgres">Datavase (sqlite, rqlite, postgres)<a class="headerlink" href="#datavase-sqlite-rqlite-postgres" title="Permalink to this headline">¶</a></h3>
+<p>As of v0.8, Netmaker uses sqlite by default as a database. It can also use PostgreSQL, or rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users.</p>
 <p>Additional database support (besides sqlite and rqlite) is very easy to implement for special use cases. Netmaker uses simple key value lookups to run the networks, and the database was designed to be extensible, so support for key-value stores and other SQL-based databases can be achieved by changing a single file.</p>
 
 
@@ -1002,12 +1023,12 @@
             </a>
           
           
-            <a href="quick-start.html" title="Quick Install"
+            <a href="install.html" title="Install"
                class="md-flex md-footer-nav__link md-footer-nav__link--next"
                rel="next">
             <div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title"><span
                 class="md-flex__ellipsis"> <span
-                class="md-footer-nav__direction"> Next </span> Quick Install </span>
+                class="md-footer-nav__direction"> Next </span> Install </span>
             </div>
             <div class="md-flex__cell md-flex__cell--shrink"><i
                 class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>

+ 26 - 5
docs/_build/html/client-installation.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Client Installation &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Client Installation &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -462,6 +469,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 26 - 5
docs/_build/html/conduct.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Code of Conduct &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Code of Conduct &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -462,6 +469,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 13 - 6
docs/_build/html/external-clients.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>External Clients &#8212; Netmaker 0.8.2 documentation</title>
+    <title>External Clients &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -473,7 +473,14 @@
     <li class="md-nav__item">
     
     
-      <a href="server-installation.html#highly-available-installation" class="md-nav__link">Highly Available Installation</a>
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
       
     
     </li></ul>

+ 13 - 6
docs/_build/html/genindex.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Index &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Index &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -79,7 +79,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -165,7 +165,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -177,13 +177,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -471,7 +471,14 @@
     <li class="md-nav__item">
     
     
-      <a href="server-installation.html#highly-available-installation" class="md-nav__link">Highly Available Installation</a>
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
       
     
     </li></ul>

+ 26 - 5
docs/_build/html/getting-started.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Getting Started &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Getting Started &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -490,6 +497,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 15 - 7
docs/_build/html/index.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Welcome to the Netmaker Documentation &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Welcome to the Netmaker Documentation &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -80,7 +80,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="#" title="Netmaker 0.8.2 documentation"
+        <a href="#" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -166,7 +166,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="#" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="#" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -178,13 +178,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="#" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="#" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="#"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -472,7 +472,14 @@
     <li class="md-nav__item">
     
     
-      <a href="server-installation.html#highly-available-installation" class="md-nav__link">Highly Available Installation</a>
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
       
     
     </li></ul>
@@ -888,7 +895,8 @@
 <li class="toctree-l2"><a class="reference internal" href="server-installation.html#linux-install-without-docker">Linux Install without Docker</a></li>
 <li class="toctree-l2"><a class="reference internal" href="server-installation.html#kubernetes-install">Kubernetes Install</a></li>
 <li class="toctree-l2"><a class="reference internal" href="server-installation.html#nginx-reverse-proxy-setup-with-https">Nginx Reverse Proxy Setup with https</a></li>
-<li class="toctree-l2"><a class="reference internal" href="server-installation.html#highly-available-installation">Highly Available Installation</a></li>
+<li class="toctree-l2"><a class="reference internal" href="server-installation.html#highly-available-installation-kubernetes">Highly Available Installation (Kubernetes)</a></li>
+<li class="toctree-l2"><a class="reference internal" href="server-installation.html#highly-available-installation-vms-bare-metal">Highly Available Installation (VMs/Bare Metal)</a></li>
 </ul>
 </li>
 </ul>

+ 13 - 6
docs/_build/html/install.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Install &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Install &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -482,7 +482,14 @@
     <li class="md-nav__item">
     
     
-      <a href="server-installation.html#highly-available-installation" class="md-nav__link">Highly Available Installation</a>
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
       
     
     </li></ul>

+ 26 - 5
docs/_build/html/license.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>License &#8212; Netmaker 0.8.2 documentation</title>
+    <title>License &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -80,7 +80,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -166,7 +166,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -178,13 +178,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -274,6 +274,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -461,6 +468,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

BIN
docs/_build/html/objects.inv


+ 26 - 5
docs/_build/html/quick-start-nginx.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Install with Nginx (depreciated) &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Install with Nginx (depreciated) &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -506,6 +513,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 29 - 8
docs/_build/html/quick-start.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Quick Install &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Quick Install &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -57,7 +57,7 @@
     <link rel="index" title="Index" href="genindex.html" />
     <link rel="search" title="Search" href="search.html" />
     <link rel="next" title="Getting Started" href="getting-started.html" />
-    <link rel="prev" title="Architecture" href="architecture.html" />
+    <link rel="prev" title="Install" href="install.html" />
   
    
 
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -496,6 +503,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>
@@ -930,7 +951,7 @@
     <div class="md-footer-nav">
       <nav class="md-footer-nav__inner md-grid">
           
-            <a href="architecture.html" title="Architecture"
+            <a href="install.html" title="Install"
                class="md-flex md-footer-nav__link md-footer-nav__link--prev"
                rel="prev">
               <div class="md-flex__cell md-flex__cell--shrink">
@@ -939,7 +960,7 @@
               <div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
                 <span class="md-flex__ellipsis">
                   <span
-                      class="md-footer-nav__direction"> Previous </span> Architecture </span>
+                      class="md-footer-nav__direction"> Previous </span> Install </span>
               </div>
             </a>
           

+ 13 - 6
docs/_build/html/search.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Search &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Search &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     
@@ -85,7 +85,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -171,7 +171,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -183,13 +183,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -477,7 +477,14 @@
     <li class="md-nav__item">
     
     
-      <a href="server-installation.html#highly-available-installation" class="md-nav__link">Highly Available Installation</a>
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
       
     
     </li></ul>

File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/searchindex.js


+ 190 - 14
docs/_build/html/server-installation.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Advanced Server Installation &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Advanced Server Installation &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -467,7 +474,25 @@
         </li>
         <li class="md-nav__item"><a href="#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
         </li>
-        <li class="md-nav__item"><a href="#highly-available-installation" class="md-nav__link">Highly Available Installation</a><nav class="md-nav">
+        <li class="md-nav__item"><a href="#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a><nav class="md-nav">
+              <ul class="md-nav__list">
+        <li class="md-nav__item"><a href="#requirements" class="md-nav__link">Requirements</a>
+        </li>
+        <li class="md-nav__item"><a href="#recommended-settings" class="md-nav__link">Recommended Settings:</a>
+        </li>
+        <li class="md-nav__item"><a href="#example-installations" class="md-nav__link">Example Installations:</a>
+        </li>
+        <li class="md-nav__item"><a href="#ingress" class="md-nav__link">Ingress</a>
+        </li>
+        <li class="md-nav__item"><a href="#kernel-wireguard" class="md-nav__link">Kernel WireGuard</a>
+        </li>
+        <li class="md-nav__item"><a href="#dns" class="md-nav__link">DNS</a>
+        </li>
+        <li class="md-nav__item"><a href="#values" class="md-nav__link">Values</a>
+        </li></ul>
+            </nav>
+        </li>
+        <li class="md-nav__item"><a href="#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a><nav class="md-nav">
               <ul class="md-nav__list">
         <li class="md-nav__item"><a href="#load-balancer-setup" class="md-nav__link">1. Load Balancer Setup</a>
         </li>
@@ -536,7 +561,14 @@
     <li class="md-nav__item">
     
     
-      <a href="#highly-available-installation" class="md-nav__link">Highly Available Installation</a>
+      <a href="#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
       
     
     </li></ul>
@@ -861,7 +893,25 @@
         </li>
         <li class="md-nav__item"><a href="#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
         </li>
-        <li class="md-nav__item"><a href="#highly-available-installation" class="md-nav__link">Highly Available Installation</a><nav class="md-nav">
+        <li class="md-nav__item"><a href="#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a><nav class="md-nav">
+              <ul class="md-nav__list">
+        <li class="md-nav__item"><a href="#requirements" class="md-nav__link">Requirements</a>
+        </li>
+        <li class="md-nav__item"><a href="#recommended-settings" class="md-nav__link">Recommended Settings:</a>
+        </li>
+        <li class="md-nav__item"><a href="#example-installations" class="md-nav__link">Example Installations:</a>
+        </li>
+        <li class="md-nav__item"><a href="#ingress" class="md-nav__link">Ingress</a>
+        </li>
+        <li class="md-nav__item"><a href="#kernel-wireguard" class="md-nav__link">Kernel WireGuard</a>
+        </li>
+        <li class="md-nav__item"><a href="#dns" class="md-nav__link">DNS</a>
+        </li>
+        <li class="md-nav__item"><a href="#values" class="md-nav__link">Values</a>
+        </li></ul>
+            </nav>
+        </li>
+        <li class="md-nav__item"><a href="#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a><nav class="md-nav">
               <ul class="md-nav__list">
         <li class="md-nav__item"><a href="#load-balancer-setup" class="md-nav__link">1. Load Balancer Setup</a>
         </li>
@@ -890,7 +940,7 @@
 
 <h2 id="system-compatibility">System Compatibility<a class="headerlink" href="#system-compatibility" title="Permalink to this headline">¶</a></h2>
 <p>Netmaker will require elevated privileges to perform network operations. Netmaker has similar limitations to <a class="reference internal" href="client-installation.html"><span class="doc">netclient</span></a> (client networking agent).</p>
-<p>Typically, Netmaker is run inside of containers (Docker). To run a non-docker installation, you must run the Netmaker binary, CoreDNS binary, rqlite, and a web server directly on the host. Each of these components have their own individual requirements.</p>
+<p>Typically, Netmaker is run inside of containers (Docker). To run a non-docker installation, you must run the Netmaker binary, CoreDNS binary, database, and a web server directly on the host. Each of these components have their own individual requirements.</p>
 <p>The quick install guide is recommended for first-time installs.</p>
 <p>The following documents are meant for special cases like Kubernetes and LXC, or for more advanced setups.</p>
 
@@ -948,11 +998,26 @@
 <p><strong>Description:</strong> Enables DNS Mode, meaning config files will be generated for CoreDNS.</p>
 </dd>
 <dt>DATABASE:</dt><dd><p><strong>Default:</strong> “sqlite”</p>
-<p><strong>Description:</strong> Specify db type to connect with. Currently, options include “sqlite” and “rqlite”.</p>
+<p><strong>Description:</strong> Specify db type to connect with. Currently, options include “sqlite”, “rqlite”, and “postgres”.</p>
 </dd>
 <dt>SQL_CONN:</dt><dd><p><strong>Default:</strong> “<a class="reference external" href="http://">http://</a>”</p>
 <p><strong>Description:</strong> Specify the necessary string to connect with your local or remote sql database.</p>
 </dd>
+<dt>SQL_HOST:</dt><dd><p><strong>Default:</strong> “localhost”</p>
+<p><strong>Description:</strong> Host where postgres is running.</p>
+</dd>
+<dt>SQL_PORT:</dt><dd><p><strong>Default:</strong> “5432”</p>
+<p><strong>Description:</strong> port postgres is running.</p>
+</dd>
+<dt>SQL_DB:</dt><dd><p><strong>Default:</strong> “netmaker”</p>
+<p><strong>Description:</strong> DB to use in postgres.</p>
+</dd>
+<dt>SQL_USER:</dt><dd><p><strong>Default:</strong> “postgres”</p>
+<p><strong>Description:</strong> User for posgres.</p>
+</dd>
+<dt>SQL_PASS:</dt><dd><p><strong>Default:</strong> “nopass”</p>
+<p><strong>Description:</strong> Password for postgres.</p>
+</dd>
 <dt>CLIENT_MODE:</dt><dd><p><strong>Default:</strong> “on”</p>
 <p><strong>Description:</strong> Specifies if server should deploy itself as a node (client) in each network. May be turned to “off” for more restricted servers.</p>
 </dd>
@@ -1237,15 +1302,117 @@ kubectl apply -f netclient-template.yaml
 </div>
 
 
-<span id="hainstall"></span><h2 id="highly-available-installation">Highly Available Installation<a class="headerlink" href="#highly-available-installation" title="Permalink to this headline">¶</a></h2>
+<span id="hainstall"></span><h2 id="highly-available-installation-kubernetes">Highly Available Installation (Kubernetes)<a class="headerlink" href="#highly-available-installation-kubernetes" title="Permalink to this headline">¶</a></h2>
+<p>Netmaker comes with a Helm chart to deploy with High Availability on Kubernetes:</p>
+<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">helm</span> <span class="n">repo</span> <span class="n">add</span> <span class="n">netmaker</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">gravitl</span><span class="o">.</span><span class="n">github</span><span class="o">.</span><span class="n">io</span><span class="o">/</span><span class="n">netmaker</span><span class="o">-</span><span class="n">helm</span><span class="o">/</span>
+<span class="n">helm</span> <span class="n">repo</span> <span class="n">update</span>
+</pre></div>
+</div>
+
+<h3 id="requirements">Requirements<a class="headerlink" href="#requirements" title="Permalink to this headline">¶</a></h3>
+<p>To run HA Netmaker on Kubernetes, your cluster must have the following:
+- RWO and RWX Storage Classes (RWX is only required if running Netmaker with DNS Management enabled).
+- An Ingress Controller and valid TLS certificates
+- This chart can currently generate ingress for Nginx or Traefik Ingress with LetsEncrypt + Cert Manager
+- If LetsEncrypt and CertManager are not deployed, you must manually configure certificates for your ingress</p>
+<p>Furthermore, the chart will by default install and use a postgresql cluster as its datastore.</p>
+
+
+<h3 id="recommended-settings">Recommended Settings:<a class="headerlink" href="#recommended-settings" title="Permalink to this headline">¶</a></h3>
+<p>A minimal HA install of Netmaker can be run with the following command:
+<cite>helm install netmaker –generate-name –set baseDomain=nm.example.com</cite>
+This install has some notable exceptions:
+- Ingress <strong>must</strong> be manually configured post-install (need to create valid Ingress with TLS)
+- Server will use “userspace” WireGuard, which is slower than kernel WG
+- DNS will be disabled</p>
+
+
+<h3 id="example-installations">Example Installations:<a class="headerlink" href="#example-installations" title="Permalink to this headline">¶</a></h3>
+<p>An annotated install command:</p>
+<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">helm</span> <span class="n">install</span> <span class="n">netmaker</span><span class="o">/</span><span class="n">netmaker</span> <span class="o">--</span><span class="n">generate</span><span class="o">-</span><span class="n">name</span> \ <span class="c1"># generate a random id for the deploy</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">baseDomain</span><span class="o">=</span><span class="n">nm</span><span class="o">.</span><span class="n">example</span><span class="o">.</span><span class="n">com</span> \ <span class="c1"># the base wildcard domain to use for the netmaker api/dashboard/grpc ingress</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">replicas</span><span class="o">=</span><span class="mi">3</span> \ <span class="c1"># number of server replicas to deploy (3 by default)</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">enabled</span><span class="o">=</span><span class="n">true</span> \ <span class="c1"># deploy ingress automatically (requires nginx or traefik and cert-manager + letsencrypt)</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">className</span><span class="o">=</span><span class="n">nginx</span> \ <span class="c1"># ingress class to use</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">tls</span><span class="o">.</span><span class="n">issuerName</span><span class="o">=</span><span class="n">letsencrypt</span><span class="o">-</span><span class="n">prod</span> \ <span class="c1"># LetsEncrypt certificate issuer to use</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">dns</span><span class="o">.</span><span class="n">enabled</span><span class="o">=</span><span class="n">true</span> \ <span class="c1"># deploy and enable private DNS management with CoreDNS</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">dns</span><span class="o">.</span><span class="n">clusterIP</span><span class="o">=</span><span class="mf">10.245</span><span class="o">.</span><span class="mf">75.75</span> <span class="o">--</span><span class="nb">set</span> <span class="n">dns</span><span class="o">.</span><span class="n">RWX</span><span class="o">.</span><span class="n">storageClassName</span><span class="o">=</span><span class="n">nfs</span> \ <span class="c1"># required fields for DNS</span>
+<span class="o">--</span><span class="nb">set</span> <span class="n">postgresql</span><span class="o">-</span><span class="n">ha</span><span class="o">.</span><span class="n">postgresql</span><span class="o">.</span><span class="n">replicaCount</span><span class="o">=</span><span class="mi">2</span> \ <span class="c1"># number of DB replicas to deploy (default 2)</span>
+</pre></div>
+</div>
+<p>The below command will install netmaker with two server replicas, a coredns server, and ingress with routes of api.nm.example.com, grpc.nm.example.com, and dashboard.nm.example.com. CoreDNS will be reachable at 10.245.75.75, and will use NFS to share a volume with Netmaker (to configure dns entries).</p>
+<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">helm</span> <span class="n">install</span> <span class="n">netmaker</span><span class="o">/</span><span class="n">netmaker</span> <span class="o">--</span><span class="n">generate</span><span class="o">-</span><span class="n">name</span> <span class="o">--</span><span class="nb">set</span> <span class="n">baseDomain</span><span class="o">=</span><span class="n">nm</span><span class="o">.</span><span class="n">example</span><span class="o">.</span><span class="n">com</span> \
+<span class="o">--</span><span class="nb">set</span> <span class="n">replicas</span><span class="o">=</span><span class="mi">2</span> <span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">enabled</span><span class="o">=</span><span class="n">true</span> <span class="o">--</span><span class="nb">set</span> <span class="n">dns</span><span class="o">.</span><span class="n">enabled</span><span class="o">=</span><span class="n">true</span> \
+<span class="o">--</span><span class="nb">set</span> <span class="n">dns</span><span class="o">.</span><span class="n">clusterIP</span><span class="o">=</span><span class="mf">10.245</span><span class="o">.</span><span class="mf">75.75</span> <span class="o">--</span><span class="nb">set</span> <span class="n">dns</span><span class="o">.</span><span class="n">RWX</span><span class="o">.</span><span class="n">storageClassName</span><span class="o">=</span><span class="n">nfs</span> \
+<span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">className</span><span class="o">=</span><span class="n">nginx</span>
+</pre></div>
+</div>
+<p>The below command will install netmaker with three server replicas (the default), <strong>no coredns</strong>, and ingress with routes of api.netmaker.example.com, grpc.netmaker.example.com, and dashboard.netmaker.example.com. There will be one UI replica instead of two, and one database instance instead of two. Traefik will look for a ClusterIssuer named “le-prod-2” to get valid certificates for the ingress.</p>
+<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">helm3</span> <span class="n">install</span> <span class="n">netmaker</span><span class="o">/</span><span class="n">netmaker</span> <span class="o">--</span><span class="n">generate</span><span class="o">-</span><span class="n">name</span> \
+<span class="o">--</span><span class="nb">set</span> <span class="n">baseDomain</span><span class="o">=</span><span class="n">netmaker</span><span class="o">.</span><span class="n">example</span><span class="o">.</span><span class="n">com</span> <span class="o">--</span><span class="nb">set</span> <span class="n">postgresql</span><span class="o">-</span><span class="n">ha</span><span class="o">.</span><span class="n">postgresql</span><span class="o">.</span><span class="n">replicaCount</span><span class="o">=</span><span class="mi">1</span> \
+<span class="o">--</span><span class="nb">set</span> <span class="n">ui</span><span class="o">.</span><span class="n">replicas</span><span class="o">=</span><span class="mi">1</span> <span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">enabled</span><span class="o">=</span><span class="n">true</span> \
+<span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">tls</span><span class="o">.</span><span class="n">issuerName</span><span class="o">=</span><span class="n">le</span><span class="o">-</span><span class="n">prod</span><span class="o">-</span><span class="mi">2</span> <span class="o">--</span><span class="nb">set</span> <span class="n">ingress</span><span class="o">.</span><span class="n">className</span><span class="o">=</span><span class="n">traefik</span>
+</pre></div>
+</div>
+<p>Below, we discuss the considerations for Ingress, Kernel WireGuard, and DNS.</p>
+
+
+<h3 id="ingress">Ingress<a class="headerlink" href="#ingress" title="Permalink to this headline">¶</a></h3>
+<p>To run HA Netmaker, you must have ingress installed and enabled on your cluster with valid TLS certificates (not self-signed). If you are running Nginx as your Ingress Controller and LetsEncrypt for TLS certificate management, you can run the helm install with the following settings:</p>
+<ul class="simple">
+<li><p><cite>–set ingress.enabled=true</cite></p></li>
+<li><p><cite>–set ingress.annotations.cert-manager.io/cluster-issuer=&lt;your LE issuer name&gt;</cite></p></li>
+</ul>
+<p>If you are not using Nginx or Traefik and LetsEncrypt, we recommend leaving ingress.enabled=false (default), and then manually creating the ingress objects post-install. You will need three ingress objects with TLS:</p>
+<ul class="simple">
+<li><p><cite>dashboard.&lt;baseDomain&gt;</cite></p></li>
+<li><p><cite>api.&lt;baseDomain&gt;</cite></p></li>
+<li><p><cite>grpc.&lt;baseDomain&gt;</cite></p></li>
+</ul>
+<p>If deploying manually, the gRPC ingress object requires special considerations. Look up the proper way to route grpc with your ingress controller. For instance, on Traefik, an IngressRouteTCP object is required.</p>
+<p>There are some example ingress objects in the kube/example folder.</p>
+
+
+<h3 id="kernel-wireguard">Kernel WireGuard<a class="headerlink" href="#kernel-wireguard" title="Permalink to this headline">¶</a></h3>
+<p>If you have control of the Kubernetes worker node servers, we recommend <strong>first</strong> installing WireGuard on the hosts, and then installing HA Netmaker in Kernel mode. By default, Netmaker will install with userspace WireGuard (wireguard-go) for maximum compatibility, and to avoid needing permissions at the host level. If you have installed WireGuard on your hosts, you should install Netmaker’s helm chart with the following option:</p>
+<ul class="simple">
+<li><p><cite>–set wireguard.kernel=true</cite></p></li>
+</ul>
+
+
+<h3 id="dns">DNS<a class="headerlink" href="#dns" title="Permalink to this headline">¶</a></h3>
+<p>By Default, the helm chart will deploy without DNS enabled. To enable DNS, specify with:</p>
+<ul class="simple">
+<li><p><cite>–set dns.enabled=true</cite></p></li>
+</ul>
+<p>This will require specifying a RWX storage class, e.g.:</p>
+<ul class="simple">
+<li><p><cite>–set dns.RWX.storageClassName=nfs</cite></p></li>
+</ul>
+<p>This will also require specifying a service address for DNS. Choose a valid ipv4 address from the service IP CIDR for your cluster, e.g.:</p>
+<ul class="simple">
+<li><p><cite>–set dns.clusterIP=10.245.69.69</cite></p></li>
+</ul>
+<p><strong>This address will only be reachable from hosts that have access to the cluster service CIDR.</strong> It is only designed for use cases related to k8s. If you want a more general-use Netmaker server on Kubernetes for use cases outside of k8s, you will need to do one of the following:
+- bind the CoreDNS service to port 53 on one of your worker nodes and set the COREDNS_ADDRESS equal to the public IP of the worker node
+- Create a private Network with Netmaker and set the COREDNS_ADDRESS equal to the private address of the host running CoreDNS. For this, CoreDNS will need a node selector and will ideally run on the same host as one of the Netmaker server instances.</p>
+
+
+<h3 id="values">Values<a class="headerlink" href="#values" title="Permalink to this headline">¶</a></h3>
+<p>To view all options for the chart, please visit the README in the code repo <a class="reference external" href="https://github.com/gravitl/netmaker/tree/master/kube/helm#values">here</a> .</p>
+
+
+
+<h2 id="highly-available-installation-vms-bare-metal">Highly Available Installation (VMs/Bare Metal)<a class="headerlink" href="#highly-available-installation-vms-bare-metal" title="Permalink to this headline">¶</a></h2>
 <p>For an enterprise Netmaker installation, you will need a server that is highly available, to ensure redundant WireGuard routing when any server goes down. To do this, you will need:</p>
 <ol class="arabic simple">
 <li><p>A load balancer</p></li>
 <li><p>3+ Netmaker server instances</p></li>
-<li><p>rqlite as the backing database</p></li>
+<li><p>rqlite or PostgreSQL as the backing database</p></li>
 </ol>
 <p>These documents outline general HA installation guidelines. Netmaker is highly customizable to meet a wide range of enterprise environments. If you would like support with an enterprise-grade Netmaker installation, you can <a class="reference external" href="https://gravitl.com/book">schedule a consultation here</a> .</p>
-<p>The main consideration here is how to configure rqlite. Most other settings and procedures match the standardized way of making applications HA: Load balancing to multiple instances, and sharing a DB. In our case, the DB (rqlite) is distributed, making HA data more easily achievable.</p>
+<p>The main consideration for this document is how to configure rqlite. Most other settings and procedures match the standardized way of making applications HA: Load balancing to multiple instances, and sharing a DB. In our case, the DB (rqlite) is distributed, making HA data more easily achievable.</p>
+<p>If using PostgreSQL, follow their documentation for <a class="reference external" href="https://www.postgresql.org/docs/14/high-availability.html">installing in HA mode</a> and skip step #2.</p>
 
 <h3 id="load-balancer-setup">1. Load Balancer Setup<a class="headerlink" href="#load-balancer-setup" title="Permalink to this headline">¶</a></h3>
 <p>Your load balancer of choice will send requests to the Netmaker servers. Setup is similar to the various guides we have created for Nginx, Caddy, and Traefik. SSL certificates must also be configured and handled by the LB.</p>
@@ -1277,7 +1444,16 @@ kubectl apply -f netclient-template.yaml
 
 
 <h3 id="netmaker-setup">3. Netmaker Setup<a class="headerlink" href="#netmaker-setup" title="Permalink to this headline">¶</a></h3>
-<p>Netmaker will be started on each node with default settings, except with DATABASE=rqlite and SQL_CONN set appropriately to reach the local rqlite instance. Rqlite will maintain consistency with each Netmaker backend.</p>
+<p>Netmaker will be started on each node with default settings, except with DATABASE=rqlite (or DATABASE=postgress) and SQL_CONN set appropriately to reach the local rqlite instance. Rqlite will maintain consistency with each Netmaker backend.</p>
+<p>If deploying HA with PostgreSQL, you will connect with the following settings:</p>
+<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">SQL_HOST</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">sql</span> <span class="n">host</span><span class="o">&gt;</span>
+<span class="n">SQL_PORT</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">port</span><span class="o">&gt;</span>
+<span class="n">SQL_DB</span>   <span class="o">=</span> <span class="o">&lt;</span><span class="n">designated</span> <span class="n">sql</span> <span class="n">DB</span><span class="o">&gt;</span>
+<span class="n">SQL_USER</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">your</span> <span class="n">user</span><span class="o">&gt;</span>
+<span class="n">SQL_PASS</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">your</span> <span class="n">password</span><span class="o">&gt;</span>
+<span class="n">DATABASE</span> <span class="o">=</span> <span class="n">postgres</span>
+</pre></div>
+</div>
 
 
 <h3 id="other-considerations">4. Other Considerations<a class="headerlink" href="#other-considerations" title="Permalink to this headline">¶</a></h3>

+ 26 - 5
docs/_build/html/support.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Support &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Support &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -462,6 +469,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 26 - 5
docs/_build/html/troubleshoot.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Troubleshooting &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Troubleshooting &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -462,6 +469,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 26 - 5
docs/_build/html/usage.html

@@ -46,7 +46,7 @@
   
   
   
-    <title>Using Netmaker &#8212; Netmaker 0.8.2 documentation</title>
+    <title>Using Netmaker &#8212; Netmaker 0.8.4 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/material.css" />
     <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -81,7 +81,7 @@
   <nav class="md-header-nav md-grid">
     <div class="md-flex navheader">
       <div class="md-flex__cell md-flex__cell--shrink">
-        <a href="index.html" title="Netmaker 0.8.2 documentation"
+        <a href="index.html" title="Netmaker 0.8.4 documentation"
            class="md-header-nav__button md-logo">
           
             <i class="md-icon">&#xe869</i>
@@ -167,7 +167,7 @@
   <nav class="md-tabs" data-md-component="tabs">
     <div class="md-tabs__inner md-grid">
       <ul class="md-tabs__list">
-          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.2 documentation</a></li>
+          <li class="md-tabs__item"><a href="index.html" class="md-tabs__link">Netmaker 0.8.4 documentation</a></li>
       </ul>
     </div>
   </nav>
@@ -179,13 +179,13 @@
               <div class="md-sidebar__inner">
                 <nav class="md-nav md-nav--primary" data-md-level="0">
   <label class="md-nav__title md-nav__title--site" for="__drawer">
-    <a href="index.html" title="Netmaker 0.8.2 documentation" class="md-nav__button md-logo">
+    <a href="index.html" title="Netmaker 0.8.4 documentation" class="md-nav__button md-logo">
       
         <i class="md-icon">&#xe869</i>
       
     </a>
     <a href="index.html"
-       title="Netmaker 0.8.2 documentation">Netmaker Docs</a>
+       title="Netmaker 0.8.4 documentation">Netmaker Docs</a>
   </label>
     <div class="md-nav__source">
       <a href="https://github.com/gravitl/netmaker/" title="Go to repository" class="md-source" data-md-source="github">
@@ -275,6 +275,13 @@
     
     </li></ul>
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="install.html" class="md-nav__link">Install</a>
+      
+    
     </li>
     <li class="md-nav__item">
     
@@ -462,6 +469,20 @@
       <a href="server-installation.html#nginx-reverse-proxy-setup-with-https" class="md-nav__link">Nginx Reverse Proxy Setup with https</a>
       
     
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-kubernetes" class="md-nav__link">Highly Available Installation (Kubernetes)</a>
+      
+    
+    </li>
+    <li class="md-nav__item">
+    
+    
+      <a href="server-installation.html#highly-available-installation-vms-bare-metal" class="md-nav__link">Highly Available Installation (VMs/Bare Metal)</a>
+      
+    
     </li></ul>
     
     </li>

+ 5 - 5
docs/architecture.rst

@@ -77,13 +77,13 @@ Netmaker Server
 
 The Netmaker server is, at its core, a golang binary. Source code can be found `on GitHub <https://github.com/gravitl/netmaker>`_. The binary, by itself can be compiled for most systems. If you need to run the Netmaker server on a particular system, it likely can be made to work. In typical deployments, it is run as a Docker container. It can also be run as a systemd service as outlined in the non-docker install guide.
 
-The Netmaker server acts as an API to the front end, and as a GRPC server to the machines in the network. GRPC is much faster and more efficient than standard API calls, which increases the speed of transactions. For this reason, the Netmaker server exposes two ports: The default for the API is 8081, and the default for GRPC is 50051. Either the API or the GRPC server can be disabled on any given Netmaker instance can be disabled, allowing you to deploy two different servers for managing the API (which is largely for the admin's use) and GRPC (which is largely for the nodes' use).
+The Netmaker server acts as an API to the front end, and as a GRPC server to the machines in the network. GRPC is much faster and more efficient than standard API calls, which increases the speed of transactions. For this reason, the Netmaker server exposes two ports: The default for the API is 8081, and the default for GRPC is 50051. Either the API or the GRPC server can be disabled on any given Netmaker instance, allowing you to deploy two different servers for managing the API (which is largely for the admin's use) and GRPC (which is largely for the nodes' use).
 
 Most server settings are configurable via a config file, or by environment variables (which take precedence). If the server finds neither of these, it sets sensible defaults, including things like the server's reachable IP, ports, and which "modes" to run in.
 
 These modes include client mode and dns mode. Either of these can be disabled but are enabled by default. Client mode allows you to treat the Netmaker host machine (operating system) as a network Node, installing the netclient and controlling the host network. DNS mode has the server write config settings for CoreDNS, a separate component and nameserver, which picks up the config settings to manage node DNS.
 
-The Netmaker server interacts with either sqlit (default) or rqlite, a distributed version of sqlite, as its database. This DB holds information about nodes, networks, users, and other important data. This data is configuration data. For the most part, Netmaker serves configuration data to Nodes, telling them how they should configure themselves. The Netclient is the agent that actually does that configuration.
+The Netmaker server interacts with either sqlite (default), postgres, or rqlite, a distributed version of sqlite, as its database. This DB holds information about nodes, networks, users, and other important data. This data is configuration data. For the most part, Netmaker serves configuration data to Nodes, telling them how they should configure themselves. The Netclient is the agent that actually does that configuration.
 
 
 Netclient
@@ -104,10 +104,10 @@ If running in daemon mode, on a periodic basis (systemd timer), the netclient pe
 The check in process is what allows Netmaker to create dynamic mesh networks. As nodes are added to, removed from, and modified on the network, other nodes are notified, and make appropriate changes.
 
 
-sqlite and rqlite
----------------------
+Datavase (sqlite, rqlite, postgres)
+-------------------------------------
 
-As of v0.8, Netmaker uses sqlite by default as a database. It can also use rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users. 
+As of v0.8, Netmaker uses sqlite by default as a database. It can also use PostgreSQL, or rqlite, a distributed (RAFT consensus) databaseand. Netmaker interacts with this database to store and retrieve information about nodes, networks, and users. 
 
 Additional database support (besides sqlite and rqlite) is very easy to implement for special use cases. Netmaker uses simple key value lookups to run the networks, and the database was designed to be extensible, so support for key-value stores and other SQL-based databases can be achieved by changing a single file.
 

+ 1 - 1
docs/conf.py

@@ -22,7 +22,7 @@ copyright = '2021, Alex Feiszli'
 author = 'Alex Feiszli'
 
 # The full version, including alpha/beta/rc tags
-release = '0.8.3'
+release = '0.8.4'
 
 
 # -- General configuration ---------------------------------------------------

+ 162 - 10
docs/server-installation.rst

@@ -9,7 +9,7 @@ System Compatibility
 
 Netmaker will require elevated privileges to perform network operations. Netmaker has similar limitations to :doc:`netclient <./client-installation>` (client networking agent). 
 
-Typically, Netmaker is run inside of containers (Docker). To run a non-docker installation, you must run the Netmaker binary, CoreDNS binary, rqlite, and a web server directly on the host. Each of these components have their own individual requirements.
+Typically, Netmaker is run inside of containers (Docker). To run a non-docker installation, you must run the Netmaker binary, CoreDNS binary, database, and a web server directly on the host. Each of these components have their own individual requirements.
 
 The quick install guide is recommended for first-time installs. 
 
@@ -101,13 +101,38 @@ DNS_MODE:
 DATABASE:  
     **Default:** "sqlite"
 
-    **Description:** Specify db type to connect with. Currently, options include "sqlite" and "rqlite".
+    **Description:** Specify db type to connect with. Currently, options include "sqlite", "rqlite", and "postgres".
 
-SQL_CONN:  
+SQL_CONN:
     **Default:** "http://"
 
     **Description:** Specify the necessary string to connect with your local or remote sql database.
 
+SQL_HOST:
+    **Default:** "localhost"
+
+    **Description:** Host where postgres is running.
+
+SQL_PORT:
+    **Default:** "5432"
+
+    **Description:** port postgres is running.
+
+SQL_DB:
+    **Default:** "netmaker"
+
+    **Description:** DB to use in postgres.
+
+SQL_USER:
+    **Default:** "postgres"
+
+    **Description:** User for posgres.
+
+SQL_PASS:
+    **Default:** "nopass"
+
+    **Description:** Password for postgres.
+
 CLIENT_MODE:  
     **Default:** "on"
 
@@ -344,18 +369,135 @@ The following file configures Netmaker as a subdomain. This config is an adaptio
 
 .. _HAInstall:
 
-Highly Available Installation
-===============================
+
+
+Highly Available Installation (Kubernetes)
+==================================================
+
+Netmaker comes with a Helm chart to deploy with High Availability on Kubernetes:
+
+.. code-block::
+
+    helm repo add netmaker https://gravitl.github.io/netmaker-helm/
+    helm repo update
+
+Requirements
+---------------
+
+To run HA Netmaker on Kubernetes, your cluster must have the following:
+- RWO and RWX Storage Classes (RWX is only required if running Netmaker with DNS Management enabled).
+- An Ingress Controller and valid TLS certificates 
+- This chart can currently generate ingress for Nginx or Traefik Ingress with LetsEncrypt + Cert Manager
+- If LetsEncrypt and CertManager are not deployed, you must manually configure certificates for your ingress
+
+Furthermore, the chart will by default install and use a postgresql cluster as its datastore.
+
+Recommended Settings:
+----------------------
+A minimal HA install of Netmaker can be run with the following command:
+`helm install netmaker --generate-name --set baseDomain=nm.example.com`
+This install has some notable exceptions:
+- Ingress **must** be manually configured post-install (need to create valid Ingress with TLS)
+- Server will use "userspace" WireGuard, which is slower than kernel WG
+- DNS will be disabled
+
+Example Installations:
+------------------------
+An annotated install command:
+
+.. code-block::
+
+    helm install netmaker/netmaker --generate-name \ # generate a random id for the deploy 
+    --set baseDomain=nm.example.com \ # the base wildcard domain to use for the netmaker api/dashboard/grpc ingress 
+    --set replicas=3 \ # number of server replicas to deploy (3 by default) 
+    --set ingress.enabled=true \ # deploy ingress automatically (requires nginx or traefik and cert-manager + letsencrypt) 
+    --set ingress.className=nginx \ # ingress class to use 
+    --set ingress.tls.issuerName=letsencrypt-prod \ # LetsEncrypt certificate issuer to use 
+    --set dns.enabled=true \ # deploy and enable private DNS management with CoreDNS 
+    --set dns.clusterIP=10.245.75.75 --set dns.RWX.storageClassName=nfs \ # required fields for DNS 
+    --set postgresql-ha.postgresql.replicaCount=2 \ # number of DB replicas to deploy (default 2)
+
+
+The below command will install netmaker with two server replicas, a coredns server, and ingress with routes of api.nm.example.com, grpc.nm.example.com, and dashboard.nm.example.com. CoreDNS will be reachable at 10.245.75.75, and will use NFS to share a volume with Netmaker (to configure dns entries).
+
+.. code-block::
+
+    helm install netmaker/netmaker --generate-name --set baseDomain=nm.example.com \
+    --set replicas=2 --set ingress.enabled=true --set dns.enabled=true \
+    --set dns.clusterIP=10.245.75.75 --set dns.RWX.storageClassName=nfs \
+    --set ingress.className=nginx
+
+The below command will install netmaker with three server replicas (the default), **no coredns**, and ingress with routes of api.netmaker.example.com, grpc.netmaker.example.com, and dashboard.netmaker.example.com. There will be one UI replica instead of two, and one database instance instead of two. Traefik will look for a ClusterIssuer named "le-prod-2" to get valid certificates for the ingress. 
+
+.. code-block::
+
+    helm3 install netmaker/netmaker --generate-name \
+    --set baseDomain=netmaker.example.com --set postgresql-ha.postgresql.replicaCount=1 \
+    --set ui.replicas=1 --set ingress.enabled=true \
+    --set ingress.tls.issuerName=le-prod-2 --set ingress.className=traefik
+
+Below, we discuss the considerations for Ingress, Kernel WireGuard, and DNS.
+
+Ingress	
+----------
+To run HA Netmaker, you must have ingress installed and enabled on your cluster with valid TLS certificates (not self-signed). If you are running Nginx as your Ingress Controller and LetsEncrypt for TLS certificate management, you can run the helm install with the following settings:
+
+- `--set ingress.enabled=true`
+- `--set ingress.annotations.cert-manager.io/cluster-issuer=<your LE issuer name>`
+
+If you are not using Nginx or Traefik and LetsEncrypt, we recommend leaving ingress.enabled=false (default), and then manually creating the ingress objects post-install. You will need three ingress objects with TLS:
+
+- `dashboard.<baseDomain>`
+- `api.<baseDomain>`
+- `grpc.<baseDomain>`
+
+If deploying manually, the gRPC ingress object requires special considerations. Look up the proper way to route grpc with your ingress controller. For instance, on Traefik, an IngressRouteTCP object is required.
+
+There are some example ingress objects in the kube/example folder.
+
+Kernel WireGuard
+------------------
+If you have control of the Kubernetes worker node servers, we recommend **first** installing WireGuard on the hosts, and then installing HA Netmaker in Kernel mode. By default, Netmaker will install with userspace WireGuard (wireguard-go) for maximum compatibility, and to avoid needing permissions at the host level. If you have installed WireGuard on your hosts, you should install Netmaker's helm chart with the following option:
+
+- `--set wireguard.kernel=true`
+
+DNS
+----------
+By Default, the helm chart will deploy without DNS enabled. To enable DNS, specify with:
+
+- `--set dns.enabled=true` 
+
+This will require specifying a RWX storage class, e.g.:
+
+- `--set dns.RWX.storageClassName=nfs`
+
+This will also require specifying a service address for DNS. Choose a valid ipv4 address from the service IP CIDR for your cluster, e.g.:
+
+- `--set dns.clusterIP=10.245.69.69`
+
+**This address will only be reachable from hosts that have access to the cluster service CIDR.** It is only designed for use cases related to k8s. If you want a more general-use Netmaker server on Kubernetes for use cases outside of k8s, you will need to do one of the following:
+- bind the CoreDNS service to port 53 on one of your worker nodes and set the COREDNS_ADDRESS equal to the public IP of the worker node
+- Create a private Network with Netmaker and set the COREDNS_ADDRESS equal to the private address of the host running CoreDNS. For this, CoreDNS will need a node selector and will ideally run on the same host as one of the Netmaker server instances.
+
+Values
+---------
+
+To view all options for the chart, please visit the README in the code repo `here <https://github.com/gravitl/netmaker/tree/master/kube/helm#values>`_ .
+
+Highly Available Installation (VMs/Bare Metal)
+==================================================
 
 For an enterprise Netmaker installation, you will need a server that is highly available, to ensure redundant WireGuard routing when any server goes down. To do this, you will need:
 
 1. A load balancer
 2. 3+ Netmaker server instances
-3. rqlite as the backing database
+3. rqlite or PostgreSQL as the backing database
 
 These documents outline general HA installation guidelines. Netmaker is highly customizable to meet a wide range of enterprise environments. If you would like support with an enterprise-grade Netmaker installation, you can `schedule a consultation here <https://gravitl.com/book>`_ . 
 
-The main consideration here is how to configure rqlite. Most other settings and procedures match the standardized way of making applications HA: Load balancing to multiple instances, and sharing a DB. In our case, the DB (rqlite) is distributed, making HA data more easily achievable.
+The main consideration for this document is how to configure rqlite. Most other settings and procedures match the standardized way of making applications HA: Load balancing to multiple instances, and sharing a DB. In our case, the DB (rqlite) is distributed, making HA data more easily achievable.
+
+If using PostgreSQL, follow their documentation for `installing in HA mode <https://www.postgresql.org/docs/14/high-availability.html>`_ and skip step #2.
 
 1. Load Balancer Setup
 ------------------------
@@ -398,7 +540,19 @@ Once rqlite instances have been configured, the Netmaker servers can be deployed
 3. Netmaker Setup
 ------------------
 
-Netmaker will be started on each node with default settings, except with DATABASE=rqlite and SQL_CONN set appropriately to reach the local rqlite instance. Rqlite will maintain consistency with each Netmaker backend.
+Netmaker will be started on each node with default settings, except with DATABASE=rqlite (or DATABASE=postgress) and SQL_CONN set appropriately to reach the local rqlite instance. Rqlite will maintain consistency with each Netmaker backend.
+
+If deploying HA with PostgreSQL, you will connect with the following settings:
+
+.. code-block::
+
+    SQL_HOST = <sql host>
+    SQL_PORT = <port>
+    SQL_DB   = <designated sql DB>
+    SQL_USER = <your user>
+    SQL_PASS = <your password>
+    DATABASE = postgres
+
 
 4. Other Considerations
 ------------------------
@@ -408,5 +562,3 @@ This is enough to get a functioning HA installation of Netmaker. However, you ma
 
 
 
-
-

+ 51 - 4
functions/helpers.go

@@ -20,31 +20,36 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 )
 
+// PrintUserLog - prints a log with a given username
 func PrintUserLog(username string, message string, loglevel int) {
 	log.SetFlags(log.Flags() &^ (log.Llongfile | log.Lshortfile))
 	if int32(loglevel) <= servercfg.GetVerbose() && servercfg.GetVerbose() != 0 {
-		log.Println(username, message)
+		log.Println("[netmaker]", username, message)
 	}
 }
 
+// ParseNetwork - parses a network into a model
 func ParseNetwork(value string) (models.Network, error) {
 	var network models.Network
 	err := json.Unmarshal([]byte(value), &network)
 	return network, err
 }
 
+// ParseNode - parses a node into a model
 func ParseNode(value string) (models.Node, error) {
 	var node models.Node
 	err := json.Unmarshal([]byte(value), &node)
 	return node, err
 }
 
+// ParseExtClient - parses an extclient into a model
 func ParseExtClient(value string) (models.ExtClient, error) {
 	var extClient models.ExtClient
 	err := json.Unmarshal([]byte(value), &extClient)
 	return extClient, err
 }
 
+// ParseIntClient - parses int client
 func ParseIntClient(value string) (models.IntClient, error) {
 	var intClient models.IntClient
 	err := json.Unmarshal([]byte(value), &intClient)
@@ -54,6 +59,7 @@ func ParseIntClient(value string) (models.IntClient, error) {
 //Takes in an arbitrary field and value for field and checks to see if any other
 //node has that value for the same field within the network
 
+// GetUser - gets a user
 func GetUser(username string) (models.User, error) {
 
 	var user models.User
@@ -67,6 +73,7 @@ func GetUser(username string) (models.User, error) {
 	return user, err
 }
 
+// SliceContains - sees if a slice contains something
 func SliceContains(slice []string, item string) bool {
 	set := make(map[string]struct{}, len(slice))
 	for _, s := range slice {
@@ -77,6 +84,7 @@ func SliceContains(slice []string, item string) bool {
 	return ok
 }
 
+// CreateServerToken - creates a server token
 func CreateServerToken(netID string) (string, error) {
 	var network models.Network
 	var accesskey models.AccessKey
@@ -130,6 +138,7 @@ func CreateServerToken(netID string) (string, error) {
 	return accesskey.AccessString, nil
 }
 
+// GetPeersList - gets peers for given network
 func GetPeersList(networkName string) ([]models.PeersResponse, error) {
 
 	var peers []models.PeersResponse
@@ -151,6 +160,7 @@ func GetPeersList(networkName string) ([]models.PeersResponse, error) {
 	return peers, err
 }
 
+// GetIntPeersList - get int peers list
 func GetIntPeersList() ([]models.PeersResponse, error) {
 
 	var peers []models.PeersResponse
@@ -176,6 +186,7 @@ func GetIntPeersList() ([]models.PeersResponse, error) {
 	return peers, err
 }
 
+// GetServerIntClient - get server int client
 func GetServerIntClient() (*models.IntClient, error) {
 
 	intClients, err := database.FetchRecords(database.INT_CLIENTS_TABLE_NAME)
@@ -192,6 +203,7 @@ func GetServerIntClient() (*models.IntClient, error) {
 	return nil, err
 }
 
+// NetworkExists - check if network exists
 func NetworkExists(name string) (bool, error) {
 
 	var network string
@@ -201,6 +213,8 @@ func NetworkExists(name string) (bool, error) {
 	}
 	return len(network) > 0, nil
 }
+
+// GetRecordKey - get record key
 func GetRecordKey(id string, network string) (string, error) {
 	if id == "" || network == "" {
 		return "", errors.New("unable to get record key")
@@ -208,6 +222,7 @@ func GetRecordKey(id string, network string) (string, error) {
 	return id + "###" + network, nil
 }
 
+// UpdateNetworkNodeAddresses - updates network node addresses
 func UpdateNetworkNodeAddresses(networkName string) error {
 
 	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -244,6 +259,7 @@ func UpdateNetworkNodeAddresses(networkName string) error {
 	return nil
 }
 
+// NetworkNodesUpdateAction - updates action of network nodes
 func NetworkNodesUpdateAction(networkName string, action string) error {
 
 	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -277,6 +293,7 @@ func NetworkNodesUpdateAction(networkName string, action string) error {
 	return nil
 }
 
+// NetworkNodesUpdatePullChanges - tells nodes on network to pull
 func NetworkNodesUpdatePullChanges(networkName string) error {
 
 	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -308,6 +325,7 @@ func NetworkNodesUpdatePullChanges(networkName string) error {
 	return nil
 }
 
+// UpdateNetworkLocalAddresses - updates network localaddresses
 func UpdateNetworkLocalAddresses(networkName string) error {
 
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -346,6 +364,7 @@ func UpdateNetworkLocalAddresses(networkName string) error {
 	return nil
 }
 
+// IsNetworkDisplayNameUnique - checks if network display name unique
 func IsNetworkDisplayNameUnique(name string) (bool, error) {
 
 	isunique := true
@@ -365,6 +384,7 @@ func IsNetworkDisplayNameUnique(name string) (bool, error) {
 	return isunique, nil
 }
 
+// IsMacAddressUnique - checks if mac is unique
 func IsMacAddressUnique(macaddress string, networkName string) (bool, error) {
 
 	_, err := database.FetchRecord(database.NODES_TABLE_NAME, macaddress+"###"+networkName)
@@ -375,6 +395,7 @@ func IsMacAddressUnique(macaddress string, networkName string) (bool, error) {
 	return true, nil
 }
 
+// GetNetworkNonServerNodeCount - get number of network non server nodes
 func GetNetworkNonServerNodeCount(networkName string) (int, error) {
 
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -400,6 +421,8 @@ func GetNetworkNonServerNodeCount(networkName string) (int, error) {
 //Does so by checking against all keys and seeing if any have the same value
 //may want to hash values before comparing...consider this
 //TODO: No error handling!!!!
+
+// IsKeyValid - check if key is valid
 func IsKeyValid(networkname string, keyvalue string) bool {
 
 	network, _ := GetParentNetwork(networkname)
@@ -422,6 +445,7 @@ func IsKeyValid(networkname string, keyvalue string) bool {
 	return isvalid
 }
 
+// IsKeyValidGlobal - checks if a key is valid globally
 func IsKeyValidGlobal(keyvalue string) bool {
 
 	networks, _ := models.GetNetworks()
@@ -453,6 +477,8 @@ func IsKeyValidGlobal(keyvalue string) bool {
 //This just gets a network object from a network name
 //Should probably just be GetNetwork. kind of a dumb name.
 //Used in contexts where it's not the Parent network.
+
+// GetParentNetwork - get parent network
 func GetParentNetwork(networkname string) (models.Network, error) {
 
 	var network models.Network
@@ -466,6 +492,7 @@ func GetParentNetwork(networkname string) (models.Network, error) {
 	return network, nil
 }
 
+// IsIpNet - checks if valid ip
 func IsIpNet(host string) bool {
 	return net.ParseIP(host) != nil
 }
@@ -473,6 +500,8 @@ func IsIpNet(host string) bool {
 //Similar to above but checks if Cidr range is valid
 //At least this guy's got some print statements
 //still not good error handling
+
+// IsIpCIDR - IsIpCIDR
 func IsIpCIDR(host string) bool {
 
 	ip, ipnet, err := net.ParseCIDR(host)
@@ -488,6 +517,8 @@ func IsIpCIDR(host string) bool {
 
 //This  checks to  make sure a network name is valid.
 //Switch to REGEX?
+
+// NameInNetworkCharSet - see if name is in charset for networks
 func NameInNetworkCharSet(name string) bool {
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-_."
@@ -500,6 +531,7 @@ func NameInNetworkCharSet(name string) bool {
 	return true
 }
 
+// NameInDNSCharSet - name in dns char set
 func NameInDNSCharSet(name string) bool {
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-."
@@ -512,6 +544,7 @@ func NameInDNSCharSet(name string) bool {
 	return true
 }
 
+// NameInNodeCharSet - name in node char set
 func NameInNodeCharSet(name string) bool {
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-"
@@ -528,6 +561,8 @@ func NameInNodeCharSet(name string) bool {
 //The mac address acts as the Unique ID for nodes.
 //Is this a dumb thing to do? I thought it was cool but maybe it's dumb.
 //It doesn't really provide a tangible benefit over a random ID
+
+// GetNodeByMacAddress - gets a node by mac address
 func GetNodeByMacAddress(network string, macaddress string) (models.Node, error) {
 
 	var node models.Node
@@ -551,6 +586,7 @@ func GetNodeByMacAddress(network string, macaddress string) (models.Node, error)
 	return node, nil
 }
 
+// GetDeletedNodeByMacAddress - get a deleted node
 func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node, error) {
 
 	var node models.Node
@@ -574,10 +610,12 @@ func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node,
 	return node, nil
 }
 
+// RemoveDeletedNode - remove deleted node
 func RemoveDeletedNode(nodeid string) bool {
 	return database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, nodeid) == nil
 }
 
+// DeleteAllIntClients - delete all int clients
 func DeleteAllIntClients() error {
 	err := database.DeleteAllRecords(database.INT_CLIENTS_TABLE_NAME)
 	if err != nil {
@@ -586,6 +624,7 @@ func DeleteAllIntClients() error {
 	return nil
 }
 
+// GetAllIntClients - get all int clients
 func GetAllIntClients() ([]models.IntClient, error) {
 	var clients []models.IntClient
 	collection, err := database.FetchRecords(database.INT_CLIENTS_TABLE_NAME)
@@ -607,6 +646,7 @@ func GetAllIntClients() ([]models.IntClient, error) {
 	return clients, nil
 }
 
+// GetAllExtClients - get all ext clients
 func GetAllExtClients() ([]models.ExtClient, error) {
 	var extclients []models.ExtClient
 	collection, err := database.FetchRecords(database.EXT_CLIENT_TABLE_NAME)
@@ -633,6 +673,8 @@ func GetAllExtClients() ([]models.ExtClient, error) {
 //and checks against all nodes to see if it's taken, until it finds one.
 //TODO: We do not handle a case where we run out of addresses.
 //We will need to handle that eventually
+
+// UniqueAddress - see if address is unique
 func UniqueAddress(networkName string) (string, error) {
 
 	var network models.Network
@@ -669,6 +711,7 @@ func UniqueAddress(networkName string) (string, error) {
 	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
 }
 
+// UniqueAddress6 - see if ipv6 address is unique
 func UniqueAddress6(networkName string) (string, error) {
 
 	var network models.Network
@@ -701,7 +744,7 @@ func UniqueAddress6(networkName string) (string, error) {
 	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
 }
 
-//generate an access key value
+// GenKey - generates access key
 func GenKey() string {
 
 	var seededRand *rand.Rand = rand.New(
@@ -721,6 +764,8 @@ func GenKey() string {
 //we should probably just have 1 random string generator
 //that  can be used across all functions
 //have a "base string" a "length" and a "charset"
+
+// GenKeyName - generates a key name
 func GenKeyName() string {
 
 	var seededRand *rand.Rand = rand.New(
@@ -736,6 +781,7 @@ func GenKeyName() string {
 	return "key" + string(b)
 }
 
+// IsIPUnique - checks if an IP is unique
 func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 
 	isunique := true
@@ -766,6 +812,7 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 
 //called once key has been used by createNode
 //reduces value by one and deletes if necessary
+// DecrimentKey - decriments key uses
 func DecrimentKey(networkName string, keyvalue string) {
 
 	var network models.Network
@@ -796,7 +843,7 @@ func DecrimentKey(networkName string, keyvalue string) {
 	}
 }
 
-//takes the logic from controllers.deleteKey
+// DeleteKey - deletes a key
 func DeleteKey(network models.Network, i int) {
 
 	network.AccessKeys = append(network.AccessKeys[:i],
@@ -809,7 +856,7 @@ func DeleteKey(network models.Network, i int) {
 	}
 }
 
-//increments an IP over the previous
+// Inc - increments an IP
 func Inc(ip net.IP) {
 	for j := len(ip) - 1; j >= 0; j-- {
 		ip[j]++

+ 3 - 1
functions/jwt.go

@@ -3,6 +3,7 @@ package functions
 import (
 	"errors"
 	"time"
+
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
@@ -29,6 +30,7 @@ func CreateJWT(macaddress string, network string) (response string, err error) {
 	return "", err
 }
 
+// CreateUserJWT - creates a user jwt token
 func CreateUserJWT(username string, networks []string, isadmin bool) (response string, err error) {
 	expirationTime := time.Now().Add(60 * 12 * time.Minute)
 	claims := &models.UserClaims{
@@ -70,7 +72,7 @@ func VerifyUserToken(tokenString string) (username string, networks []string, is
 	return "", nil, false, err
 }
 
-// GRPC [nodes] Only
+// VerifyToken - gRPC [nodes] Only
 func VerifyToken(tokenString string) (macaddress string, network string, err error) {
 	claims := &models.Claims{}
 

+ 25 - 25
functions/local.go

@@ -14,31 +14,31 @@ func FileExists(f string) bool {
 }
 
 func SetDNSDir() error {
-        dir, err := os.Getwd()
-        if err != nil {
-                return err
-        }
-        _, err = os.Stat(dir + "/config/dnsconfig")
-        if os.IsNotExist(err) {
-                os.Mkdir(dir+"/config/dnsconfig", 0744)
-        } else if err != nil {
-                PrintUserLog("","couldnt find or create /config/dnsconfig",0)
-                return err
-        }
-		_, err = os.Stat(dir + "/config/dnsconfig/Corefile")
-        if os.IsNotExist(err) {
-			err = SetCorefile(".")
-			if err != nil {
-				PrintUserLog("",err.Error(),0)
-			}
+	dir, err := os.Getwd()
+	if err != nil {
+		return err
+	}
+	_, err = os.Stat(dir + "/config/dnsconfig")
+	if os.IsNotExist(err) {
+		os.Mkdir(dir+"/config/dnsconfig", 0744)
+	} else if err != nil {
+		PrintUserLog("", "couldnt find or create /config/dnsconfig", 0)
+		return err
+	}
+	_, err = os.Stat(dir + "/config/dnsconfig/Corefile")
+	if os.IsNotExist(err) {
+		err = SetCorefile(".")
+		if err != nil {
+			PrintUserLog("", err.Error(), 0)
 		}
-		_, err = os.Stat(dir + "/config/dnsconfig/netmaker.hosts")
-        if os.IsNotExist(err) {
-			_, err = os.Create(dir + "/config/dnsconfig/netmaker.hosts")
-			if err != nil {
-				PrintUserLog("",err.Error(),0)
-			}
-		}		
+	}
+	_, err = os.Stat(dir + "/config/dnsconfig/netmaker.hosts")
+	if os.IsNotExist(err) {
+		_, err = os.Create(dir + "/config/dnsconfig/netmaker.hosts")
+		if err != nil {
+			PrintUserLog("", err.Error(), 0)
+		}
+	}
 	return nil
 }
 
@@ -51,7 +51,7 @@ func SetCorefile(domains string) error {
 	if os.IsNotExist(err) {
 		os.Mkdir(dir+"/config/dnsconfig", 744)
 	} else if err != nil {
-		PrintUserLog("","couldnt find or create /config/dnsconfig",0)
+		PrintUserLog("", "couldnt find or create /config/dnsconfig", 0)
 		return err
 	}
 

+ 1 - 0
go.mod

@@ -8,6 +8,7 @@ require (
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
+	github.com/lib/pq v1.10.3
 	github.com/mattn/go-sqlite3 v1.14.8
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e

+ 19 - 0
go.sum

@@ -1,8 +1,12 @@
+cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -16,7 +20,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -31,6 +37,7 @@ github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6u
 github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
 github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -54,12 +61,14 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
 github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -73,6 +82,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozI
 github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
+github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -110,10 +121,13 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -154,6 +168,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -185,6 +200,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
 golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -196,6 +212,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@@ -206,6 +223,7 @@ golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfM
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210913210325-91d1988e44de h1:M9Jc92kgqmVmidpnOeegP2VgO2DfHEcsUWtWMmBwNFQ=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210913210325-91d1988e44de/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@@ -234,6 +252,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 1 - 0
logic/extpeers.go

@@ -8,6 +8,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 )
 
+// GetExtPeersList - gets the ext peers lists
 func GetExtPeersList(macaddress string, networkName string) ([]models.ExtPeersResponse, error) {
 
 	var peers []models.ExtPeersResponse

+ 110 - 0
logic/network.go

@@ -0,0 +1,110 @@
+package logic
+
+import (
+	"net"
+	"os/exec"
+	"strings"
+
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+// GetLocalIP - gets the local ip
+func GetLocalIP(node models.Node) string {
+
+	var local string
+
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return local
+	}
+	_, localrange, err := net.ParseCIDR(node.LocalRange)
+	if err != nil {
+		return local
+	}
+
+	found := false
+	for _, i := range ifaces {
+		if i.Flags&net.FlagUp == 0 {
+			continue // interface down
+		}
+		if i.Flags&net.FlagLoopback != 0 {
+			continue // loopback interface
+		}
+		addrs, err := i.Addrs()
+		if err != nil {
+			return local
+		}
+		for _, addr := range addrs {
+			var ip net.IP
+			switch v := addr.(type) {
+			case *net.IPNet:
+				if !found {
+					ip = v.IP
+					local = ip.String()
+					if node.IsLocal == "yes" {
+						found = localrange.Contains(ip)
+					} else {
+						found = true
+					}
+				}
+			case *net.IPAddr:
+				if !found {
+					ip = v.IP
+					local = ip.String()
+					if node.IsLocal == "yes" {
+						found = localrange.Contains(ip)
+
+					} else {
+						found = true
+					}
+				}
+			}
+		}
+	}
+	return local
+}
+
+// == Private ==
+
+func deleteInterface(ifacename string, postdown string) error {
+	var err error
+	if !ncutils.IsKernel() {
+		err = RemoveConf(ifacename, true)
+	} else {
+		ipExec, errN := exec.LookPath("ip")
+		err = errN
+		if err != nil {
+			ncutils.PrintLog(err.Error(), 1)
+		}
+		_, err = ncutils.RunCmd(ipExec+" link del "+ifacename, false)
+		if postdown != "" {
+			runcmds := strings.Split(postdown, "; ")
+			err = ncutils.RunCmds(runcmds, true)
+		}
+	}
+	return err
+}
+
+func isInterfacePresent(iface string, address string) (string, bool) {
+	var interfaces []net.Interface
+	var err error
+	interfaces, err = net.Interfaces()
+	if err != nil {
+		Log("ERROR: could not read interfaces", 0)
+		return "", true
+	}
+	for _, currIface := range interfaces {
+		var currAddrs []net.Addr
+		currAddrs, err = currIface.Addrs()
+		if err != nil || len(currAddrs) == 0 {
+			continue
+		}
+		for _, addr := range currAddrs {
+			if strings.Contains(addr.String(), address) && currIface.Name != iface {
+				return currIface.Name, false
+			}
+		}
+	}
+	return "", true
+}

+ 5 - 1
logic/nodes.go

@@ -10,6 +10,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 )
 
+// GetNetworkNodes - gets the nodes of a network
 func GetNetworkNodes(network string) ([]models.Node, error) {
 	var nodes []models.Node
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -33,6 +34,7 @@ func GetNetworkNodes(network string) ([]models.Node, error) {
 	return nodes, nil
 }
 
+// GetSortedNetworkServerNodes - gets nodes of a network, except sorted by update time
 func GetSortedNetworkServerNodes(network string) ([]models.Node, error) {
 	var nodes []models.Node
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -57,6 +59,7 @@ func GetSortedNetworkServerNodes(network string) ([]models.Node, error) {
 	return nodes, nil
 }
 
+// GetPeers - gets the peers of a given node
 func GetPeers(node models.Node) ([]models.Node, error) {
 	if node.IsServer == "yes" && IsLeader(&node) {
 		SetNetworkServerPeers(&node)
@@ -73,10 +76,11 @@ func GetPeers(node models.Node) ([]models.Node, error) {
 	return peers, nil
 }
 
+// IsLeader - determines if a given server node is a leader
 func IsLeader(node *models.Node) bool {
 	nodes, err := GetSortedNetworkServerNodes(node.Network)
 	if err != nil {
-		functions.PrintUserLog("[netmaker]", "ERROR: COULD NOT RETRIEVE SERVER NODES. THIS WILL BREAK HOLE PUNCHING.", 0)
+		functions.PrintUserLog("", "ERROR: COULD NOT RETRIEVE SERVER NODES. THIS WILL BREAK HOLE PUNCHING.", 0)
 		return false
 	}
 	for _, n := range nodes {

+ 474 - 0
logic/server.go

@@ -0,0 +1,474 @@
+package logic
+
+import (
+	"errors"
+	"net"
+	"os"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+// == Join, Checkin, and Leave for Server ==
+
+// KUBERNETES_LISTEN_PORT - starting port for Kubernetes in order to use NodePort range
+const KUBERNETES_LISTEN_PORT = 31821
+const KUBERNETES_SERVER_MTU = 1024
+
+// ServerJoin - responsible for joining a server to a network
+func ServerJoin(network string, serverID string, privateKey string) error {
+
+	if network == "" {
+		return errors.New("no network provided")
+	}
+
+	var err error
+	var node *models.Node // fill this object with server node specifics
+	node = &models.Node{
+		IsServer:     "yes",
+		DNSOn:        "no",
+		IsStatic:     "yes",
+		Name:         models.NODE_SERVER_NAME,
+		MacAddress:   serverID,
+		UDPHolePunch: "no",
+	}
+	node.SetDefaults()
+
+	if servercfg.GetPlatform() == "Kubernetes" {
+		node.ListenPort = KUBERNETES_LISTEN_PORT
+		node.MTU = KUBERNETES_SERVER_MTU
+	}
+
+	if node.LocalRange != "" && node.LocalAddress == "" {
+		Log("local vpn, getting local address from range: "+node.LocalRange, 1)
+		node.LocalAddress = GetLocalIP(*node)
+	}
+
+	if node.Endpoint == "" {
+		if node.IsLocal == "yes" && node.LocalAddress != "" {
+			node.Endpoint = node.LocalAddress
+		} else {
+			node.Endpoint, err = ncutils.GetPublicIP()
+		}
+		if err != nil || node.Endpoint == "" {
+			Log("Error setting server node Endpoint.", 0)
+			return err
+		}
+	}
+
+	// Generate and set public/private WireGuard Keys
+	if privateKey == "" {
+		wgPrivatekey, err := wgtypes.GeneratePrivateKey()
+		if err != nil {
+			Log(err.Error(), 1)
+			return err
+		}
+		privateKey = wgPrivatekey.String()
+		node.PublicKey = wgPrivatekey.PublicKey().String()
+	}
+	// should never set mac address for server anymore
+
+	var postnode *models.Node
+	postnode = &models.Node{
+		Password:            node.Password,
+		MacAddress:          node.MacAddress,
+		AccessKey:           node.AccessKey,
+		Network:             network,
+		ListenPort:          node.ListenPort,
+		PostUp:              node.PostUp,
+		PostDown:            node.PostDown,
+		PersistentKeepalive: node.PersistentKeepalive,
+		LocalAddress:        node.LocalAddress,
+		Interface:           node.Interface,
+		PublicKey:           node.PublicKey,
+		DNSOn:               node.DNSOn,
+		Name:                node.Name,
+		Endpoint:            node.Endpoint,
+		SaveConfig:          node.SaveConfig,
+		UDPHolePunch:        node.UDPHolePunch,
+	}
+
+	Log("adding a server instance on network "+postnode.Network, 2)
+	*node, err = CreateNode(*postnode, network)
+	if err != nil {
+		return err
+	}
+	err = SetNetworkNodesLastModified(node.Network)
+	if err != nil {
+		return err
+	}
+
+	// get free port based on returned default listen port
+	node.ListenPort, err = ncutils.GetFreePort(node.ListenPort)
+	if err != nil {
+		Log("Error retrieving port: "+err.Error(), 2)
+	}
+
+	// safety check. If returned node from server is local, but not currently configured as local, set to local addr
+	if node.IsLocal == "yes" && node.LocalRange != "" {
+		node.LocalAddress, err = ncutils.GetLocalIP(node.LocalRange)
+		if err != nil {
+			return err
+		}
+		node.Endpoint = node.LocalAddress
+	}
+
+	node.SetID()
+	if err = StorePrivKey(node.ID, privateKey); err != nil {
+		return err
+	}
+	if err = ServerPush(node.MacAddress, node.Network); err != nil {
+		return err
+	}
+
+	peers, hasGateway, gateways, err := GetServerPeers(node.MacAddress, network, node.IsDualStack == "yes", node.IsIngressGateway == "yes")
+	if err != nil && !ncutils.IsEmptyRecord(err) {
+		Log("failed to retrieve peers", 1)
+		return err
+	}
+
+	err = initWireguard(node, privateKey, peers, hasGateway, gateways)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ServerCheckin - runs pulls and pushes for server
+func ServerCheckin(mac string, network string) error {
+	var serverNode models.Node
+	var newNode *models.Node
+	var err error
+	serverNode, err = GetNode(mac, network)
+	if err != nil {
+		return err
+	}
+
+	newNode, err = ServerPull(mac, network, false)
+	if isDeleteError(err) {
+		return ServerLeave(mac, network)
+	} else if err != nil {
+		return err
+	}
+
+	actionCompleted := checkNodeActions(newNode, network, &serverNode)
+	if actionCompleted == models.NODE_DELETE {
+		return errors.New("node has been removed")
+	}
+
+	return ServerPush(newNode.MacAddress, newNode.Network)
+}
+
+// ServerPull - pulls current config/peers for server
+func ServerPull(mac string, network string, onErr bool) (*models.Node, error) {
+
+	var serverNode models.Node
+	var err error
+	serverNode, err = GetNode(mac, network)
+	if err != nil {
+		return &serverNode, err
+	}
+
+	if serverNode.IPForwarding == "yes" {
+		if err = setIPForwardingLinux(); err != nil {
+			return &serverNode, err
+		}
+	}
+	serverNode.OS = runtime.GOOS
+
+	if serverNode.PullChanges == "yes" || onErr {
+		// check for interface change
+		var isIfacePresent bool
+		var oldIfaceName string
+		// checks if address is in use by another interface
+		oldIfaceName, isIfacePresent = isInterfacePresent(serverNode.Interface, serverNode.Address)
+		if !isIfacePresent {
+			if err = deleteInterface(oldIfaceName, serverNode.PostDown); err != nil {
+				Log("could not delete old interface "+oldIfaceName, 1)
+			}
+			Log("removed old interface "+oldIfaceName, 1)
+		}
+		serverNode.PullChanges = "no"
+		if err = setWGConfig(serverNode, network, false); err != nil {
+			return &serverNode, err
+		}
+		// handle server side update
+		if err = serverNode.Update(&serverNode); err != nil {
+			return &serverNode, err
+		}
+	} else {
+		if err = setWGConfig(serverNode, network, true); err != nil {
+			if errors.Is(err, os.ErrNotExist) {
+				return ServerPull(serverNode.MacAddress, serverNode.Network, true)
+			} else {
+				return &serverNode, err
+			}
+		}
+	}
+
+	return &serverNode, nil
+}
+
+// ServerPush - pushes config changes for server checkins/join
+func ServerPush(mac string, network string) error {
+
+	var serverNode models.Node
+	var err error
+	serverNode, err = GetNode(mac, network)
+	if err != nil /* && !ncutils.IsEmptyRecord(err) May not be necessary */ {
+		return err
+	}
+	serverNode.OS = runtime.GOOS
+	serverNode.SetLastCheckIn()
+	return serverNode.Update(&serverNode)
+}
+
+// ServerLeave - removes a server node
+func ServerLeave(mac string, network string) error {
+
+	var serverNode models.Node
+	var err error
+	serverNode, err = GetNode(mac, network)
+	if err != nil {
+		return err
+	}
+	serverNode.SetID()
+	return DeleteNode(&serverNode, true)
+}
+
+// GetServerPeers - gets peers of server
+func GetServerPeers(macaddress string, network string, dualstack bool, isIngressGateway bool) ([]wgtypes.PeerConfig, bool, []string, error) {
+	hasGateway := false
+	var err error
+	var gateways []string
+	var peers []wgtypes.PeerConfig
+	var nodecfg models.Node
+	var nodes []models.Node // fill above fields from server or client
+
+	nodecfg, err = GetNode(macaddress, network)
+	if err != nil {
+		return nil, hasGateway, gateways, err
+	}
+	nodes, err = GetPeers(nodecfg)
+	if err != nil {
+		return nil, hasGateway, gateways, err
+	}
+
+	keepalive := nodecfg.PersistentKeepalive
+	keepalivedur, err := time.ParseDuration(strconv.FormatInt(int64(keepalive), 10) + "s")
+	keepaliveserver, err := time.ParseDuration(strconv.FormatInt(int64(5), 10) + "s")
+	if err != nil {
+		Log("Issue with format of keepalive value. Please view server config. "+err.Error(), 1)
+		return nil, hasGateway, gateways, err
+	}
+
+	for _, node := range nodes {
+		pubkey, err := wgtypes.ParseKey(node.PublicKey)
+		if err != nil {
+			Log("error parsing key "+pubkey.String(), 1)
+			return peers, hasGateway, gateways, err
+		}
+
+		if nodecfg.PublicKey == node.PublicKey {
+			continue
+		}
+		if nodecfg.Endpoint == node.Endpoint {
+			if nodecfg.LocalAddress != node.LocalAddress && node.LocalAddress != "" {
+				node.Endpoint = node.LocalAddress
+			} else {
+				continue
+			}
+		}
+
+		var peer wgtypes.PeerConfig
+		var peeraddr = net.IPNet{
+			IP:   net.ParseIP(node.Address),
+			Mask: net.CIDRMask(32, 32),
+		}
+		var allowedips []net.IPNet
+		allowedips = append(allowedips, peeraddr)
+		// handle manually set peers
+		for _, allowedIp := range node.AllowedIPs {
+			if _, ipnet, err := net.ParseCIDR(allowedIp); err == nil {
+				nodeEndpointArr := strings.Split(node.Endpoint, ":")
+				if !ipnet.Contains(net.IP(nodeEndpointArr[0])) && ipnet.IP.String() != node.Address { // don't need to add an allowed ip that already exists..
+					allowedips = append(allowedips, *ipnet)
+				}
+			} else if appendip := net.ParseIP(allowedIp); appendip != nil && allowedIp != node.Address {
+				ipnet := net.IPNet{
+					IP:   net.ParseIP(allowedIp),
+					Mask: net.CIDRMask(32, 32),
+				}
+				allowedips = append(allowedips, ipnet)
+			}
+		}
+		// handle egress gateway peers
+		if node.IsEgressGateway == "yes" {
+			hasGateway = true
+			ranges := node.EgressGatewayRanges
+			for _, iprange := range ranges { // go through each cidr for egress gateway
+				_, ipnet, err := net.ParseCIDR(iprange) // confirming it's valid cidr
+				if err != nil {
+					Log("could not parse gateway IP range. Not adding "+iprange, 1)
+					continue // if can't parse CIDR
+				}
+				nodeEndpointArr := strings.Split(node.Endpoint, ":") // getting the public ip of node
+				if ipnet.Contains(net.ParseIP(nodeEndpointArr[0])) { // ensuring egress gateway range does not contain public ip of node
+					Log("egress IP range of "+iprange+" overlaps with "+node.Endpoint+", omitting", 2)
+					continue // skip adding egress range if overlaps with node's ip
+				}
+				if ipnet.Contains(net.ParseIP(nodecfg.LocalAddress)) { // ensuring egress gateway range does not contain public ip of node
+					Log("egress IP range of "+iprange+" overlaps with "+nodecfg.LocalAddress+", omitting", 2)
+					continue // skip adding egress range if overlaps with node's local ip
+				}
+				gateways = append(gateways, iprange)
+				if err != nil {
+					Log("ERROR ENCOUNTERED SETTING GATEWAY", 1)
+				} else {
+					allowedips = append(allowedips, *ipnet)
+				}
+			}
+		}
+		if node.Address6 != "" && dualstack {
+			var addr6 = net.IPNet{
+				IP:   net.ParseIP(node.Address6),
+				Mask: net.CIDRMask(128, 128),
+			}
+			allowedips = append(allowedips, addr6)
+		}
+		if nodecfg.IsServer == "yes" && !(node.IsServer == "yes") {
+			peer = wgtypes.PeerConfig{
+				PublicKey:                   pubkey,
+				PersistentKeepaliveInterval: &keepaliveserver,
+				ReplaceAllowedIPs:           true,
+				AllowedIPs:                  allowedips,
+			}
+		} else if keepalive != 0 {
+			peer = wgtypes.PeerConfig{
+				PublicKey:                   pubkey,
+				PersistentKeepaliveInterval: &keepalivedur,
+				Endpoint: &net.UDPAddr{
+					IP:   net.ParseIP(node.Endpoint),
+					Port: int(node.ListenPort),
+				},
+				ReplaceAllowedIPs: true,
+				AllowedIPs:        allowedips,
+			}
+		} else {
+			peer = wgtypes.PeerConfig{
+				PublicKey: pubkey,
+				Endpoint: &net.UDPAddr{
+					IP:   net.ParseIP(node.Endpoint),
+					Port: int(node.ListenPort),
+				},
+				ReplaceAllowedIPs: true,
+				AllowedIPs:        allowedips,
+			}
+		}
+		peers = append(peers, peer)
+	}
+	if isIngressGateway {
+		extPeers, err := GetServerExtPeers(macaddress, network, dualstack)
+		if err == nil {
+			peers = append(peers, extPeers...)
+		} else {
+			Log("ERROR RETRIEVING EXTERNAL PEERS ON SERVER", 1)
+		}
+	}
+	return peers, hasGateway, gateways, err
+}
+
+// GetServerExtPeers - gets the extpeers for a client
+func GetServerExtPeers(macaddress string, network string, dualstack bool) ([]wgtypes.PeerConfig, error) {
+	var peers []wgtypes.PeerConfig
+	var nodecfg models.Node
+	var extPeers []models.Node
+	var err error
+	// fill above fields from either client or server
+
+	nodecfg, err = GetNode(macaddress, network)
+	if err != nil {
+		return nil, err
+	}
+	var tempPeers []models.ExtPeersResponse
+	tempPeers, err = GetExtPeersList(nodecfg.MacAddress, nodecfg.Network)
+	if err != nil {
+		return nil, err
+	}
+	for i := 0; i < len(tempPeers); i++ {
+		extPeers = append(extPeers, models.Node{
+			Address:             tempPeers[i].Address,
+			Address6:            tempPeers[i].Address6,
+			Endpoint:            tempPeers[i].Endpoint,
+			PublicKey:           tempPeers[i].PublicKey,
+			PersistentKeepalive: tempPeers[i].KeepAlive,
+			ListenPort:          tempPeers[i].ListenPort,
+			LocalAddress:        tempPeers[i].LocalAddress,
+		})
+	}
+	for _, extPeer := range extPeers {
+		pubkey, err := wgtypes.ParseKey(extPeer.PublicKey)
+		if err != nil {
+			return peers, err
+		}
+
+		if nodecfg.PublicKey == extPeer.PublicKey {
+			continue
+		}
+
+		var peer wgtypes.PeerConfig
+		var peeraddr = net.IPNet{
+			IP:   net.ParseIP(extPeer.Address),
+			Mask: net.CIDRMask(32, 32),
+		}
+		var allowedips []net.IPNet
+		allowedips = append(allowedips, peeraddr)
+
+		if extPeer.Address6 != "" && dualstack {
+			var addr6 = net.IPNet{
+				IP:   net.ParseIP(extPeer.Address6),
+				Mask: net.CIDRMask(128, 128),
+			}
+			allowedips = append(allowedips, addr6)
+		}
+		peer = wgtypes.PeerConfig{
+			PublicKey:         pubkey,
+			ReplaceAllowedIPs: true,
+			AllowedIPs:        allowedips,
+		}
+		peers = append(peers, peer)
+	}
+	return peers, err
+}
+
+// == Private ==
+
+func isDeleteError(err error) bool {
+	return err != nil && strings.Contains(err.Error(), models.NODE_DELETE)
+}
+
+func checkNodeActions(node *models.Node, networkName string, localNode *models.Node) string {
+	if (node.Action == models.NODE_UPDATE_KEY || localNode.Action == models.NODE_UPDATE_KEY) &&
+		node.IsStatic != "yes" {
+		err := setWGKeyConfig(*node)
+		if err != nil {
+			Log("unable to process reset keys request: "+err.Error(), 1)
+			return ""
+		}
+	}
+	if node.Action == models.NODE_DELETE || localNode.Action == models.NODE_DELETE {
+		err := ServerLeave(node.MacAddress, networkName)
+		if err != nil {
+			Log("error deleting locally: "+err.Error(), 1)
+		}
+		return models.NODE_DELETE
+	}
+	return ""
+}

+ 47 - 0
logic/serverconf.go

@@ -0,0 +1,47 @@
+package logic
+
+import (
+	"encoding/json"
+
+	"github.com/gravitl/netmaker/database"
+)
+
+type serverData struct {
+	PrivateKey string `json:"privatekey,omitempty" bson:"privatekey,omitempty"`
+}
+
+// StorePrivKey - stores server client WireGuard privatekey if needed
+func StorePrivKey(serverID string, privateKey string) error {
+	var newData *serverData
+	newData = &serverData{}
+	var err error
+	var data []byte
+	newData.PrivateKey = privateKey
+	data, err = json.Marshal(newData)
+	if err != nil {
+		return err
+	}
+	return database.Insert(serverID, string(data), database.SERVERCONF_TABLE_NAME)
+}
+
+// FetchPrivKey - fetches private key
+func FetchPrivKey(serverID string) (string, error) {
+	var dbData string
+	var err error
+	var fetchedData serverData
+	fetchedData = serverData{}
+	dbData, err = database.FetchRecord(database.SERVERCONF_TABLE_NAME, serverID)
+	if err != nil {
+		return "", err
+	}
+	err = json.Unmarshal([]byte(dbData), &fetchedData)
+	if err != nil {
+		return "", err
+	}
+	return fetchedData.PrivateKey, nil
+}
+
+// RemovePrivKey - removes a private key
+func RemovePrivKey(serverID string) error {
+	return database.DeleteRecord(database.SERVERCONF_TABLE_NAME, serverID)
+}

+ 57 - 13
logic/util.go

@@ -2,31 +2,37 @@
 package logic
 
 import (
+	"encoding/base64"
 	"encoding/json"
+	"log"
 	"strconv"
 	"strings"
 	"time"
-	"encoding/base64"
+
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/dnslogic"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/relay"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 )
 
-//This is used to validate public keys (make sure they're base64 encoded like all public keys should be).
+// IsBase64 - checks if a string is in base64 format
+// This is used to validate public keys (make sure they're base64 encoded like all public keys should be).
 func IsBase64(s string) bool {
 	_, err := base64.StdEncoding.DecodeString(s)
 	return err == nil
 }
 
+// CheckEndpoint - checks if an endpoint is valid
 func CheckEndpoint(endpoint string) bool {
 	endpointarr := strings.Split(endpoint, ":")
 	return len(endpointarr) == 2
 }
 
+// SetNetworkServerPeers - sets the network server peers of a given node
 func SetNetworkServerPeers(node *models.Node) {
 	if currentPeersList, err := GetSystemPeers(node); err == nil {
 		if database.SetPeers(currentPeersList, node.Network) {
@@ -38,9 +44,11 @@ func SetNetworkServerPeers(node *models.Node) {
 	}
 }
 
-
-func DeleteNode(key string, exterminate bool) error {
+// DeleteNode - deletes a node from database or moves into delete nodes table
+func DeleteNode(node *models.Node, exterminate bool) error {
 	var err error
+	node.SetID()
+	var key = node.ID
 	if !exterminate {
 		args := strings.Split(key, "###")
 		node, err := GetNode(args[0], args[1])
@@ -58,19 +66,19 @@ func DeleteNode(key string, exterminate bool) error {
 		}
 	} else {
 		if err := database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, key); err != nil {
-			functions.PrintUserLog("", err.Error(), 2)
+			Log(err.Error(), 2)
 		}
 	}
-	if err := database.DeleteRecord(database.NODES_TABLE_NAME, key); err != nil {
+	if err = database.DeleteRecord(database.NODES_TABLE_NAME, key); err != nil {
 		return err
 	}
 	if servercfg.IsDNSMode() {
 		err = dnslogic.SetDNS()
 	}
-	return err
+	return removeLocalServer(node)
 }
 
-
+// CreateNode - creates a node in database
 func CreateNode(node models.Node, networkName string) (models.Node, error) {
 
 	//encrypt that password so we never see it
@@ -86,8 +94,12 @@ func CreateNode(node models.Node, networkName string) (models.Node, error) {
 	if node.Name == models.NODE_SERVER_NAME {
 		node.IsServer = "yes"
 	}
-	if servercfg.IsDNSMode() && node.DNSOn == "" {
-		node.DNSOn = "yes"
+	if node.DNSOn == "" {
+		if servercfg.IsDNSMode() {
+			node.DNSOn = "yes"
+		} else {
+			node.DNSOn = "no"
+		}
 	}
 	node.SetDefaults()
 	node.Address, err = functions.UniqueAddress(networkName)
@@ -130,6 +142,7 @@ func CreateNode(node models.Node, networkName string) (models.Node, error) {
 	return node, err
 }
 
+// SetNetworkNodesLastModified - sets the network nodes last modified
 func SetNetworkNodesLastModified(networkName string) error {
 
 	timestamp := time.Now().Unix()
@@ -150,6 +163,7 @@ func SetNetworkNodesLastModified(networkName string) error {
 	return nil
 }
 
+// GetNode - fetches a node from database
 func GetNode(macaddress string, network string) (models.Node, error) {
 	var node models.Node
 
@@ -173,6 +187,7 @@ func GetNode(macaddress string, network string) (models.Node, error) {
 	return node, err
 }
 
+// GetNodePeers - fetches peers for a given node
 func GetNodePeers(networkName string, excludeRelayed bool) ([]models.Node, error) {
 	var peers []models.Node
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
@@ -180,19 +195,19 @@ func GetNodePeers(networkName string, excludeRelayed bool) ([]models.Node, error
 		if database.IsEmptyRecord(err) {
 			return peers, nil
 		}
-		functions.PrintUserLog("", err.Error(), 2)
+		Log(err.Error(), 2)
 		return nil, err
 	}
 	udppeers, errN := database.GetPeers(networkName)
 	if errN != nil {
-		functions.PrintUserLog("", errN.Error(), 2)
+		Log(errN.Error(), 2)
 	}
 	for _, value := range collection {
 		var node models.Node
 		var peer models.Node
 		err := json.Unmarshal([]byte(value), &node)
 		if err != nil {
-			functions.PrintUserLog("", err.Error(), 2)
+			Log(err.Error(), 2)
 			continue
 		}
 		if node.IsEgressGateway == "yes" { // handle egress stuff
@@ -229,6 +244,7 @@ func GetNodePeers(networkName string, excludeRelayed bool) ([]models.Node, error
 	return peers, err
 }
 
+// GetPeersList - gets the peers of a given network
 func GetPeersList(networkName string, excludeRelayed bool, relayedNodeAddr string) ([]models.Node, error) {
 	var peers []models.Node
 	var relayNode models.Node
@@ -270,6 +286,7 @@ func setPeerInfo(node models.Node) models.Node {
 	peer.IsRelayed = node.IsRelayed
 	peer.PublicKey = node.PublicKey
 	peer.Endpoint = node.Endpoint
+	peer.Name = node.Name
 	peer.LocalAddress = node.LocalAddress
 	peer.ListenPort = node.ListenPort
 	peer.AllowedIPs = node.AllowedIPs
@@ -283,3 +300,30 @@ func setPeerInfo(node models.Node) models.Node {
 	peer.IsPending = node.IsPending
 	return peer
 }
+
+func Log(message string, loglevel int) {
+	log.SetFlags(log.Flags() &^ (log.Llongfile | log.Lshortfile))
+	if int32(loglevel) <= servercfg.GetVerbose() && servercfg.GetVerbose() != 0 {
+		log.Println("[netmaker] " + message)
+	}
+}
+
+// == Private Methods ==
+
+func setIPForwardingLinux() error {
+	out, err := ncutils.RunCmd("sysctl net.ipv4.ip_forward", true)
+	if err != nil {
+		log.Println("WARNING: Error encountered setting ip forwarding. This can break functionality.")
+		return err
+	} else {
+		s := strings.Fields(string(out))
+		if s[2] != "1" {
+			_, err = ncutils.RunCmd("sysctl -w net.ipv4.ip_forward=1", true)
+			if err != nil {
+				log.Println("WARNING: Error encountered setting ip forwarding. You may want to investigate this.")
+				return err
+			}
+		}
+	}
+	return nil
+}

+ 334 - 0
logic/wireguard.go

@@ -1,10 +1,23 @@
 package logic
 
 import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"strconv"
+	"strings"
+	"time"
+
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/ncutils"
 	"golang.zx2c4.com/wireguard/wgctrl"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
+// GetSystemPeers - gets the server peers
 func GetSystemPeers(node *models.Node) (map[string]string, error) {
 	peers := make(map[string]string)
 
@@ -23,3 +36,324 @@ func GetSystemPeers(node *models.Node) (map[string]string, error) {
 	}
 	return peers, nil
 }
+
+// RemoveConf - removes a configuration for a given WireGuard interface
+func RemoveConf(iface string, printlog bool) error {
+	var err error
+	confPath := ncutils.GetNetclientPathSpecific() + iface + ".conf"
+	err = removeWGQuickConf(confPath, printlog)
+	return err
+}
+
+// == Private Methods ==
+
+func setWGConfig(node models.Node, network string, peerupdate bool) error {
+
+	node.SetID()
+	peers, hasGateway, gateways, err := GetServerPeers(node.MacAddress, node.Network, node.IsDualStack == "yes", node.IsIngressGateway == "yes")
+	if err != nil {
+		return err
+	}
+	privkey, err := FetchPrivKey(node.ID)
+	if err != nil {
+		return err
+	}
+	if peerupdate {
+		var iface string
+		iface = node.Interface
+		err = setServerPeers(iface, node.PersistentKeepalive, peers)
+	} else {
+		err = initWireguard(&node, privkey, peers, hasGateway, gateways)
+	}
+	Log("finished setting wg config on server "+node.Name, 1)
+	return err
+}
+
+func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
+
+	key, err := wgtypes.ParseKey(privkey)
+	if err != nil {
+		return err
+	}
+
+	wgclient, err := wgctrl.New()
+	if err != nil {
+		return err
+	}
+	defer wgclient.Close()
+
+	var ifacename string
+	if node.Interface != "" {
+		ifacename = node.Interface
+	} else {
+		Log("no server interface provided to configure", 2)
+	}
+	if node.Address == "" {
+		Log("no server address to provided configure", 2)
+	}
+
+	if ncutils.IsKernel() {
+		Log("setting kernel device "+ifacename, 2)
+		setKernelDevice(ifacename, node.Address)
+	}
+
+	nodeport := int(node.ListenPort)
+	var conf wgtypes.Config
+	conf = wgtypes.Config{
+		PrivateKey:   &key,
+		ListenPort:   &nodeport,
+		ReplacePeers: true,
+		Peers:        peers,
+	}
+
+	if !ncutils.IsKernel() {
+		var newConf string
+		newConf, _ = ncutils.CreateUserSpaceConf(node.Address, key.String(), strconv.FormatInt(int64(node.ListenPort), 10), node.MTU, node.PersistentKeepalive, peers)
+		confPath := ncutils.GetNetclientPathSpecific() + ifacename + ".conf"
+		Log("writing wg conf file to: "+confPath, 1)
+		err = ioutil.WriteFile(confPath, []byte(newConf), 0644)
+		if err != nil {
+			Log("error writing wg conf file to "+confPath+": "+err.Error(), 1)
+			return err
+		}
+		// spin up userspace + apply the conf file
+		var deviceiface string
+		deviceiface = ifacename
+		d, _ := wgclient.Device(deviceiface)
+		for d != nil && d.Name == deviceiface {
+			_ = RemoveConf(ifacename, false) // remove interface first
+			time.Sleep(time.Second >> 2)
+			d, _ = wgclient.Device(deviceiface)
+		}
+		time.Sleep(time.Second >> 2)
+		err = applyWGQuickConf(confPath)
+		if err != nil {
+			Log("failed to create wireguard interface", 1)
+			return err
+		}
+	} else {
+		ipExec, err := exec.LookPath("ip")
+		if err != nil {
+			return err
+		}
+
+		_, err = wgclient.Device(ifacename)
+		if err != nil {
+			if os.IsNotExist(err) {
+				fmt.Println("Device does not exist: ")
+				fmt.Println(err)
+			} else {
+				return errors.New("Unknown config error: " + err.Error())
+			}
+		}
+
+		err = wgclient.ConfigureDevice(ifacename, conf)
+		if err != nil {
+			if os.IsNotExist(err) {
+				fmt.Println("Device does not exist: ")
+				fmt.Println(err)
+			} else {
+				fmt.Printf("This is inconvenient: %v", err)
+			}
+		}
+
+		if _, err := ncutils.RunCmd(ipExec+" link set down dev "+ifacename, false); err != nil {
+			Log("attempted to remove interface before editing", 2)
+			return err
+		}
+
+		if node.PostDown != "" {
+			runcmds := strings.Split(node.PostDown, "; ")
+			_ = ncutils.RunCmds(runcmds, true)
+		}
+		// set MTU of node interface
+		if _, err := ncutils.RunCmd(ipExec+" link set mtu "+strconv.Itoa(int(node.MTU))+" up dev "+ifacename, true); err != nil {
+			Log("failed to create interface with mtu "+ifacename, 2)
+			return err
+		}
+
+		if node.PostUp != "" {
+			runcmds := strings.Split(node.PostUp, "; ")
+			_ = ncutils.RunCmds(runcmds, true)
+		}
+		if hasGateway {
+			for _, gateway := range gateways {
+				_, _ = ncutils.RunCmd(ipExec+" -4 route add "+gateway+" dev "+ifacename, true)
+			}
+		}
+		if node.Address6 != "" && node.IsDualStack == "yes" {
+			log.Println("[netclient] adding address: "+node.Address6, 1)
+			_, _ = ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+node.Address6+"/64", true)
+		}
+	}
+
+	return err
+}
+
+func setKernelDevice(ifacename string, address string) error {
+	ipExec, err := exec.LookPath("ip")
+	if err != nil {
+		return err
+	}
+
+	_, _ = ncutils.RunCmd("ip link delete dev "+ifacename, false)
+	_, _ = ncutils.RunCmd(ipExec+" link add dev "+ifacename+" type wireguard", true)
+	_, _ = ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+address+"/24", true) // this is a bug waiting to happen
+
+	return nil
+}
+
+func applyWGQuickConf(confPath string) error {
+	if _, err := ncutils.RunCmd("wg-quick up "+confPath, true); err != nil {
+		return err
+	}
+	return nil
+}
+
+func removeWGQuickConf(confPath string, printlog bool) error {
+	if _, err := ncutils.RunCmd("wg-quick down "+confPath, printlog); err != nil {
+		return err
+	}
+	return nil
+}
+
+func setServerPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
+
+	client, err := wgctrl.New()
+	if err != nil {
+		Log("failed to start wgctrl", 0)
+		return err
+	}
+
+	device, err := client.Device(iface)
+	if err != nil {
+		Log("failed to parse interface", 0)
+		return err
+	}
+	devicePeers := device.Peers
+	if len(devicePeers) > 1 && len(peers) == 0 {
+		Log("no peers pulled", 1)
+		return err
+	}
+
+	for _, peer := range peers {
+
+		for _, currentPeer := range devicePeers {
+			if currentPeer.AllowedIPs[0].String() == peer.AllowedIPs[0].String() &&
+				currentPeer.PublicKey.String() != peer.PublicKey.String() {
+				_, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
+				if err != nil {
+					log.Println("error removing peer", peer.Endpoint.String())
+				}
+			}
+		}
+		udpendpoint := peer.Endpoint.String()
+		var allowedips string
+		var iparr []string
+		for _, ipaddr := range peer.AllowedIPs {
+			iparr = append(iparr, ipaddr.String())
+		}
+		allowedips = strings.Join(iparr, ",")
+		keepAliveString := strconv.Itoa(int(keepalive))
+		if keepAliveString == "0" {
+			keepAliveString = "5"
+		}
+		if peer.Endpoint != nil {
+			_, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+
+				" endpoint "+udpendpoint+
+				" persistent-keepalive "+keepAliveString+
+				" allowed-ips "+allowedips, true)
+		} else {
+			_, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+
+				" persistent-keepalive "+keepAliveString+
+				" allowed-ips "+allowedips, true)
+		}
+		if err != nil {
+			Log("error setting peer "+peer.PublicKey.String(), 1)
+		}
+	}
+
+	for _, currentPeer := range devicePeers {
+		shouldDelete := true
+		for _, peer := range peers {
+			if peer.AllowedIPs[0].String() == currentPeer.AllowedIPs[0].String() {
+				shouldDelete = false
+			}
+		}
+		if shouldDelete {
+			output, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
+			if err != nil {
+				log.Println(output, "error removing peer", currentPeer.PublicKey.String())
+			}
+		}
+	}
+
+	return nil
+}
+
+func setWGKeyConfig(node models.Node) error {
+
+	node.SetID()
+	privatekey, err := wgtypes.GeneratePrivateKey()
+	if err != nil {
+		return err
+	}
+	privkeystring := privatekey.String()
+	publickey := privatekey.PublicKey()
+
+	node.PublicKey = publickey.String()
+
+	err = StorePrivKey(node.ID, privkeystring)
+	if err != nil {
+		return err
+	}
+	if node.Action == models.NODE_UPDATE_KEY {
+		node.Action = models.NODE_NOOP
+	}
+
+	return setWGConfig(node, node.Network, false)
+}
+
+func removeLocalServer(node *models.Node) error {
+	var ifacename = node.Interface
+	var err error
+	if ifacename != "" {
+		if !ncutils.IsKernel() {
+			if err = RemoveConf(ifacename, true); err == nil {
+				Log("removed WireGuard interface: "+ifacename, 1)
+			}
+		} else {
+			ipExec, err := exec.LookPath("ip")
+			if err != nil {
+				return err
+			}
+			out, err := ncutils.RunCmd(ipExec+" link del "+ifacename, false)
+			dontprint := strings.Contains(out, "does not exist") || strings.Contains(out, "Cannot find device")
+			if err != nil && !dontprint {
+				Log("error running command: "+ipExec+" link del "+ifacename, 1)
+				Log(out, 1)
+			}
+			if node.PostDown != "" {
+				runcmds := strings.Split(node.PostDown, "; ")
+				_ = ncutils.RunCmds(runcmds, false)
+			}
+		}
+	}
+	home := ncutils.GetNetclientPathSpecific()
+	if ncutils.FileExists(home + "netconfig-" + node.Network) {
+		_ = os.Remove(home + "netconfig-" + node.Network)
+	}
+	if ncutils.FileExists(home + "nettoken-" + node.Network) {
+		_ = os.Remove(home + "nettoken-" + node.Network)
+	}
+	if ncutils.FileExists(home + "secret-" + node.Network) {
+		_ = os.Remove(home + "secret-" + node.Network)
+	}
+	if ncutils.FileExists(home + "wgkey-" + node.Network) {
+		_ = os.Remove(home + "wgkey-" + node.Network)
+	}
+	if ncutils.FileExists(home + "nm-" + node.Network + ".conf") {
+		_ = os.Remove(home + "nm-" + node.Network + ".conf")
+	}
+	return err
+}

+ 3 - 2
main.go

@@ -120,7 +120,8 @@ func runClient(wg *sync.WaitGroup) {
 			if err := serverctl.HandleContainedClient(); err != nil {
 				// PASS
 			}
-			time.Sleep(time.Second * 15)
+			var checkintime = time.Duration(servercfg.GetServerCheckinInterval()) * time.Second
+			time.Sleep(checkintime)
 		}
 	}()
 }
@@ -156,7 +157,7 @@ func runGRPC(wg *sync.WaitGroup) {
 			log.Fatalf("Failed to serve: %v", err)
 		}
 	}()
-	log.Println("Agent Server succesfully started on port " + grpcport + " (gRPC)")
+	log.Println("Agent Server successfully started on port " + grpcport + " (gRPC)")
 
 	// Right way to stop the server using a SHUTDOWN HOOK
 	// Create a channel to receive OS signals

+ 17 - 17
models/accessToken.go

@@ -7,27 +7,27 @@ type AccessToken struct {
 }
 
 type ClientConfig struct {
-  Network string `json:"network"`
-  Key string `json:"key"`
-  LocalRange string `json:"localrange"`
+	Network    string `json:"network"`
+	Key        string `json:"key"`
+	LocalRange string `json:"localrange"`
 }
 
 type ServerConfig struct {
-  CoreDNSAddr string `json:"corednsaddr"`
-  APIConnString string `json:"apiconn"`
-  APIHost   string  `json:"apihost"`
-  APIPort   string `json:"apiport"`
-  GRPCConnString string `json:"grpcconn"`
-  GRPCHost   string `json:"grpchost"`
-  GRPCPort   string `json:"grpcport"`
-  GRPCSSL   string `json:"grpcssl"`
-  CheckinInterval   string `json:"checkininterval"`
+	CoreDNSAddr     string `json:"corednsaddr"`
+	APIConnString   string `json:"apiconn"`
+	APIHost         string `json:"apihost"`
+	APIPort         string `json:"apiport"`
+	GRPCConnString  string `json:"grpcconn"`
+	GRPCHost        string `json:"grpchost"`
+	GRPCPort        string `json:"grpcport"`
+	GRPCSSL         string `json:"grpcssl"`
+	CheckinInterval string `json:"checkininterval"`
 }
 
 type WG struct {
-  GRPCWireGuard  string  `json:"grpcwg"`
-  GRPCWGAddress  string `json:"grpcwgaddr"`
-  GRPCWGPort  string  `json:"grpcwgport"`
-  GRPCWGPubKey  string  `json:"grpcwgpubkey"`
-  GRPCWGEndpoint  string  `json:"grpcwgendpoint"`
+	GRPCWireGuard  string `json:"grpcwg"`
+	GRPCWGAddress  string `json:"grpcwgaddr"`
+	GRPCWGPort     string `json:"grpcwgport"`
+	GRPCWGPubKey   string `json:"grpcwgpubkey"`
+	GRPCWGEndpoint string `json:"grpcwgendpoint"`
 }

+ 2 - 4
models/extclient.go

@@ -6,6 +6,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 )
 
+// ExtClient - struct for external clients
 type ExtClient struct {
 	ClientID               string `json:"clientid" bson:"clientid"`
 	Description            string `json:"description" bson:"description"`
@@ -18,10 +19,7 @@ type ExtClient struct {
 	LastModified           int64  `json:"lastmodified" bson:"lastmodified"`
 }
 
-/**
- * Get the egress gateway ips of a given ExtClient struct
- * returns as []string
- */
+// ExtClient.GetEgressRangesOnNetwork - returns the egress ranges on network of ext client
 func (client *ExtClient) GetEgressRangesOnNetwork() ([]string, error) {
 
 	var result []string

+ 14 - 14
models/intclient.go

@@ -1,18 +1,18 @@
 package models
 
 type IntClient struct {
-        ClientID       string             `json:"clientid" bson:"clientid"`
-        PrivateKey     string             `json:"privatekey" bson:"privatekey"`
-        PublicKey      string             `json:"publickey" bson:"publickey"`
-        AccessKey      string             `json:"accesskey" bson:"accesskey"`
-        Address        string             `json:"address" bson:"address"`
-        Address6       string             `json:"address6" bson:"address6"`
-        Network        string             `json:"network" bson:"network"`
-        ServerPublicEndpoint  string `json:"serverpublicendpoint" bson:"serverpublicendpoint"`
-        ServerAPIPort  string      `json:"serverapiport" bson:"serverapiport"`
-        ServerPrivateAddress  string       `json:"serverprivateaddress" bson:"serverprivateaddress"`
-        ServerWGPort     string             `json:"serverwgport" bson:"serverwgport"`
-        ServerGRPCPort     string             `json:"servergrpcport" bson:"servergrpcport"`
-        ServerKey      string             `json:"serverkey" bson:"serverkey"`
-        IsServer       string             `json:"isserver" bson:"isserver"`
+	ClientID             string `json:"clientid" bson:"clientid"`
+	PrivateKey           string `json:"privatekey" bson:"privatekey"`
+	PublicKey            string `json:"publickey" bson:"publickey"`
+	AccessKey            string `json:"accesskey" bson:"accesskey"`
+	Address              string `json:"address" bson:"address"`
+	Address6             string `json:"address6" bson:"address6"`
+	Network              string `json:"network" bson:"network"`
+	ServerPublicEndpoint string `json:"serverpublicendpoint" bson:"serverpublicendpoint"`
+	ServerAPIPort        string `json:"serverapiport" bson:"serverapiport"`
+	ServerPrivateAddress string `json:"serverprivateaddress" bson:"serverprivateaddress"`
+	ServerWGPort         string `json:"serverwgport" bson:"serverwgport"`
+	ServerGRPCPort       string `json:"servergrpcport" bson:"servergrpcport"`
+	ServerKey            string `json:"serverkey" bson:"serverkey"`
+	IsServer             string `json:"isserver" bson:"isserver"`
 }

+ 46 - 37
models/network.go

@@ -12,43 +12,45 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 )
 
-//Network Struct
+// Network Struct - contains info for a given unique network
 //At  some point, need to replace all instances of Name with something else like  Identifier
 type Network struct {
-	AddressRange           string      `json:"addressrange" bson:"addressrange" validate:"required,cidr"`
-	AddressRange6          string      `json:"addressrange6" bson:"addressrange6" validate:"regexp=^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"`
-	DisplayName            string      `json:"displayname,omitempty" bson:"displayname,omitempty" validate:"omitempty,min=1,max=20,displayname_valid"`
-	NetID                  string      `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
-	NodesLastModified      int64       `json:"nodeslastmodified" bson:"nodeslastmodified"`
-	NetworkLastModified    int64       `json:"networklastmodified" bson:"networklastmodified"`
-	DefaultInterface       string      `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=15"`
-	DefaultListenPort      int32       `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"`
-	NodeLimit              int32       `json:"nodelimit" bson:"nodelimit"`
-	DefaultPostUp          string      `json:"defaultpostup" bson:"defaultpostup"`
-	DefaultPostDown        string      `json:"defaultpostdown" bson:"defaultpostdown"`
-	KeyUpdateTimeStamp     int64       `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"`
-	DefaultKeepalive       int32       `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
-	DefaultSaveConfig      string      `json:"defaultsaveconfig" bson:"defaultsaveconfig" validate:"checkyesorno"`
-	AccessKeys             []AccessKey `json:"accesskeys" bson:"accesskeys"`
-	AllowManualSignUp      string      `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
-	IsLocal                string      `json:"islocal" bson:"islocal" validate:"checkyesorno"`
-	IsDualStack            string      `json:"isdualstack" bson:"isdualstack" validate:"checkyesorno"`
-	IsIPv4                 string      `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
-	IsIPv6                 string      `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
-	IsGRPCHub              string      `json:"isgrpchub" bson:"isgrpchub" validate:"checkyesorno"`
-	LocalRange             string      `json:"localrange" bson:"localrange" validate:"omitempty,cidr"`
-	
+	AddressRange        string      `json:"addressrange" bson:"addressrange" validate:"required,cidr"`
+	AddressRange6       string      `json:"addressrange6" bson:"addressrange6" validate:"regexp=^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"`
+	DisplayName         string      `json:"displayname,omitempty" bson:"displayname,omitempty" validate:"omitempty,min=1,max=20,displayname_valid"`
+	NetID               string      `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
+	NodesLastModified   int64       `json:"nodeslastmodified" bson:"nodeslastmodified"`
+	NetworkLastModified int64       `json:"networklastmodified" bson:"networklastmodified"`
+	DefaultInterface    string      `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=15"`
+	DefaultListenPort   int32       `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"`
+	NodeLimit           int32       `json:"nodelimit" bson:"nodelimit"`
+	DefaultPostUp       string      `json:"defaultpostup" bson:"defaultpostup"`
+	DefaultPostDown     string      `json:"defaultpostdown" bson:"defaultpostdown"`
+	KeyUpdateTimeStamp  int64       `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"`
+	DefaultKeepalive    int32       `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
+	DefaultSaveConfig   string      `json:"defaultsaveconfig" bson:"defaultsaveconfig" validate:"checkyesorno"`
+	AccessKeys          []AccessKey `json:"accesskeys" bson:"accesskeys"`
+	AllowManualSignUp   string      `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
+	IsLocal             string      `json:"islocal" bson:"islocal" validate:"checkyesorno"`
+	IsDualStack         string      `json:"isdualstack" bson:"isdualstack" validate:"checkyesorno"`
+	IsIPv4              string      `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
+	IsIPv6              string      `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
+	IsGRPCHub           string      `json:"isgrpchub" bson:"isgrpchub" validate:"checkyesorno"`
+	LocalRange          string      `json:"localrange" bson:"localrange" validate:"omitempty,cidr"`
+
 	// checkin interval is depreciated at the network level. Set on server with CHECKIN_INTERVAL
-	DefaultCheckInInterval int32       `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=2,max=100000"`
-	DefaultUDPHolePunch    string      `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
-	DefaultExtClientDNS    string      `json:"defaultextclientdns" bson:"defaultextclientdns"`
-	DefaultMTU             int32       `json:"defaultmtu" bson:"defaultmtu"`
+	DefaultCheckInInterval int32  `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=2,max=100000"`
+	DefaultUDPHolePunch    string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
+	DefaultExtClientDNS    string `json:"defaultextclientdns" bson:"defaultextclientdns"`
+	DefaultMTU             int32  `json:"defaultmtu" bson:"defaultmtu"`
 }
 
+// SaveData - sensitive fields of a network that should be kept the same
 type SaveData struct { // put sensitive fields here
 	NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
 }
 
+// Network.NetIDInNetworkCharSet - checks if a netid of a network uses valid characters
 func (network *Network) NetIDInNetworkCharSet() bool {
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-_."
@@ -61,6 +63,7 @@ func (network *Network) NetIDInNetworkCharSet() bool {
 	return true
 }
 
+// Network.DisplayNameInNetworkCharSet - checks if displayname uses valid characters
 func (network *Network) DisplayNameInNetworkCharSet() bool {
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-_./;% ^#()!@$*"
@@ -73,7 +76,7 @@ func (network *Network) DisplayNameInNetworkCharSet() bool {
 	return true
 }
 
-// Anyway, returns all the networks
+// GetNetworks - returns all networks from database
 func GetNetworks() ([]Network, error) {
 	var networks []Network
 
@@ -95,6 +98,7 @@ func GetNetworks() ([]Network, error) {
 	return networks, err
 }
 
+// Network.IsNetworkDisplayNameUnique - checks if displayname is unique from other networks
 func (network *Network) IsNetworkDisplayNameUnique() (bool, error) {
 
 	isunique := true
@@ -115,7 +119,7 @@ func (network *Network) IsNetworkDisplayNameUnique() (bool, error) {
 	return isunique, nil
 }
 
-//Checks to see if any other networks have the same name (id)
+// Network.IsNetworkNameUnique - checks to see if any other networks have the same name (id)
 func (network *Network) IsNetworkNameUnique() (bool, error) {
 
 	isunique := true
@@ -136,6 +140,7 @@ func (network *Network) IsNetworkNameUnique() (bool, error) {
 	return isunique, nil
 }
 
+// Network.Validate - validates fields of an network struct
 func (network *Network) Validate(isUpdate bool) error {
 	v := validator.New()
 	_ = v.RegisterValidation("netid_valid", func(fl validator.FieldLevel) bool {
@@ -168,16 +173,17 @@ func (network *Network) Validate(isUpdate bool) error {
 	return err
 }
 
-//TODO:
-//Not  sure if we  need the below two functions. Got rid  of one of the calls. May want  to revisit
+// Network.SetNodesLastModified - sets nodes last modified on network, depricated
 func (network *Network) SetNodesLastModified() {
 	network.NodesLastModified = time.Now().Unix()
 }
 
+// Network.SetNetworkLastModified - sets network last modified time
 func (network *Network) SetNetworkLastModified() {
 	network.NetworkLastModified = time.Now().Unix()
 }
 
+// Network.SetDefaults - sets default values for a network struct
 func (network *Network) SetDefaults() {
 	if network.DefaultUDPHolePunch == "" {
 		if servercfg.IsClientMode() != "off" {
@@ -237,6 +243,7 @@ func (network *Network) SetDefaults() {
 	}
 }
 
+// Network.Update - updates a network with another network's fields
 func (currentNetwork *Network) Update(newNetwork *Network) (bool, bool, error) {
 	if err := newNetwork.Validate(true); err != nil {
 		return false, false, err
@@ -244,18 +251,19 @@ func (currentNetwork *Network) Update(newNetwork *Network) (bool, bool, error) {
 	if newNetwork.NetID == currentNetwork.NetID {
 		hasrangeupdate := newNetwork.AddressRange != currentNetwork.AddressRange
 		localrangeupdate := newNetwork.LocalRange != currentNetwork.LocalRange
-		if data, err := json.Marshal(newNetwork); err != nil {
+		data, err := json.Marshal(newNetwork)
+		if err != nil {
 			return false, false, err
-		} else {
-			newNetwork.SetNetworkLastModified()
-			err = database.Insert(newNetwork.NetID, string(data), database.NETWORKS_TABLE_NAME)
-			return hasrangeupdate, localrangeupdate, err
 		}
+		newNetwork.SetNetworkLastModified()
+		err = database.Insert(newNetwork.NetID, string(data), database.NETWORKS_TABLE_NAME)
+		return hasrangeupdate, localrangeupdate, err
 	}
 	// copy values
 	return false, false, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.")
 }
 
+// Network.SetNetworkNodesLastModified - sets network nodes last modified time
 func (network *Network) SetNetworkNodesLastModified() error {
 
 	timestamp := time.Now().Unix()
@@ -272,6 +280,7 @@ func (network *Network) SetNetworkNodesLastModified() error {
 	return nil
 }
 
+// GetNetwork - gets a network from database
 func GetNetwork(networkname string) (Network, error) {
 
 	var network Network

+ 3 - 2
models/node.go

@@ -1,13 +1,14 @@
 package models
 
 import (
+	"bytes"
 	"encoding/json"
 	"errors"
 	"math/rand"
 	"net"
 	"strings"
 	"time"
-	"bytes"
+
 	"github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"golang.org/x/crypto/bcrypt"
@@ -48,7 +49,7 @@ type Node struct {
 	ExpirationDateTime  int64    `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"`
 	LastPeerUpdate      int64    `json:"lastpeerupdate" bson:"lastpeerupdate" yaml:"lastpeerupdate"`
 	LastCheckIn         int64    `json:"lastcheckin" bson:"lastcheckin" yaml:"lastcheckin"`
-	MacAddress          string   `json:"macaddress" bson:"macaddress" yaml:"macaddress" validate:"required,mac,macaddress_unique"`
+	MacAddress          string   `json:"macaddress" bson:"macaddress" yaml:"macaddress" validate:"required,min=5,macaddress_unique"`
 	// checkin interval is depreciated at the network level. Set on server with CHECKIN_INTERVAL
 	CheckInInterval     int32    `json:"checkininterval" bson:"checkininterval" yaml:"checkininterval"`
 	Password            string   `json:"password" bson:"password" yaml:"password" validate:"required,min=6"`

+ 16 - 0
models/structs.go

@@ -2,11 +2,13 @@ package models
 
 import jwt "github.com/golang-jwt/jwt/v4"
 
+// AuthParams - struct for auth params
 type AuthParams struct {
 	MacAddress string `json:"macaddress"`
 	Password   string `json:"password"`
 }
 
+// User struct - struct for Users
 type User struct {
 	UserName string   `json:"username" bson:"username" validate:"min=3,max=40,regexp=^(([a-zA-Z,\-,\.]*)|([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4})){3,40}$"`
 	Password string   `json:"password" bson:"password" validate:"required,min=5"`
@@ -14,17 +16,20 @@ type User struct {
 	IsAdmin  bool     `json:"isadmin" bson:"isadmin"`
 }
 
+// ReturnUser - return user struct
 type ReturnUser struct {
 	UserName string   `json:"username" bson:"username" validate:"min=3,max=40,regexp=^(([a-zA-Z,\-,\.]*)|([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4})){3,40}$"`
 	Networks []string `json:"networks" bson:"networks"`
 	IsAdmin  bool     `json:"isadmin" bson:"isadmin"`
 }
 
+// UserAuthParams - user auth params struct
 type UserAuthParams struct {
 	UserName string `json:"username"`
 	Password string `json:"password"`
 }
 
+// UserClaims - user claims struct
 type UserClaims struct {
 	IsAdmin  bool
 	UserName string
@@ -32,6 +37,7 @@ type UserClaims struct {
 	jwt.StandardClaims
 }
 
+// SuccessfulUserLoginResponse - successlogin struct
 type SuccessfulUserLoginResponse struct {
 	UserName  string
 	AuthToken string
@@ -51,11 +57,13 @@ type SuccessfulLoginResponse struct {
 	AuthToken  string
 }
 
+// ErrorResponse is struct for error
 type ErrorResponse struct {
 	Code    int
 	Message string
 }
 
+// NodeAuth - struct for node auth
 type NodeAuth struct {
 	Network    string
 	Password   string
@@ -69,6 +77,7 @@ type SuccessResponse struct {
 	Response interface{}
 }
 
+// AccessKey - access key struct
 type AccessKey struct {
 	Name         string `json:"name" bson:"name" validate:"omitempty,max=20"`
 	Value        string `json:"value" bson:"value" validate:"omitempty,alphanum,max=16"`
@@ -76,17 +85,20 @@ type AccessKey struct {
 	Uses         int    `json:"uses" bson:"uses"`
 }
 
+// DisplayKey - what is displayed for key
 type DisplayKey struct {
 	Name string `json:"name" bson:"name"`
 	Uses int    `json:"uses" bson:"uses"`
 }
 
+// GlobalConfig - global config
 type GlobalConfig struct {
 	Name       string `json:"name" bson:"name"`
 	PortGRPC   string `json:"portgrpc" bson:"portgrpc"`
 	ServerGRPC string `json:"servergrpc" bson:"servergrpc"`
 }
 
+// CheckInResponse - checkin response
 type CheckInResponse struct {
 	Success          bool   `json:"success" bson:"success"`
 	NeedPeerUpdate   bool   `json:"needpeerupdate" bson:"needpeerupdate"`
@@ -97,6 +109,7 @@ type CheckInResponse struct {
 	IsPending        bool   `json:"ispending" bson:"ispending"`
 }
 
+// PeersResponse - peers response
 type PeersResponse struct {
 	PublicKey           string `json:"publickey" bson:"publickey"`
 	Endpoint            string `json:"endpoint" bson:"endpoint"`
@@ -109,6 +122,7 @@ type PeersResponse struct {
 	KeepAlive           int32  `json:"persistentkeepalive" bson:"persistentkeepalive"`
 }
 
+// ExtPeersResponse - ext peers response
 type ExtPeersResponse struct {
 	PublicKey    string `json:"publickey" bson:"publickey"`
 	Endpoint     string `json:"endpoint" bson:"endpoint"`
@@ -119,6 +133,7 @@ type ExtPeersResponse struct {
 	KeepAlive    int32  `json:"persistentkeepalive" bson:"persistentkeepalive"`
 }
 
+// EgressGatewayRequest - egress gateway request
 type EgressGatewayRequest struct {
 	NodeID      string   `json:"nodeid" bson:"nodeid"`
 	NetID       string   `json:"netid" bson:"netid"`
@@ -129,6 +144,7 @@ type EgressGatewayRequest struct {
 	PostDown    string   `json:"postdown" bson:"postdown"`
 }
 
+// RelayRequest - relay request struct
 type RelayRequest struct {
 	NodeID     string   `json:"nodeid" bson:"nodeid"`
 	NetID      string   `json:"netid" bson:"netid"`

+ 2 - 0
models/validation.go

@@ -6,10 +6,12 @@ import (
 	"github.com/go-playground/validator/v10"
 )
 
+// CheckYesOrNo - checks if a field on a struct is yes or no
 func CheckYesOrNo(fl validator.FieldLevel) bool {
 	return fl.Field().String() == "yes" || fl.Field().String() == "no"
 }
 
+// CheckRegex - check if a struct's field passes regex test
 func CheckRegex(fl validator.FieldLevel) bool {
 	re := regexp.MustCompile(fl.Param())
 	return re.MatchString(fl.Field().String())

+ 5 - 1
netclient/auth/auth.go

@@ -18,7 +18,7 @@ import (
 	"google.golang.org/grpc/status"
 )
 
-// CreateJWT func will used to create the JWT while signing in and signing out
+// SetJWT func will used to create the JWT while signing in and signing out
 func SetJWT(client nodepb.NodeServiceClient, network string) (context.Context, error) {
 	home := ncutils.GetNetclientPathSpecific()
 	tokentext, err := ioutil.ReadFile(home + "nettoken-" + network)
@@ -41,6 +41,7 @@ func SetJWT(client nodepb.NodeServiceClient, network string) (context.Context, e
 	return ctx, nil
 }
 
+// AutoLogin - auto logins whenever client needs to request from server
 func AutoLogin(client nodepb.NodeServiceClient, network string) error {
 	home := ncutils.GetNetclientPathSpecific()
 	cfg, err := config.ReadConfig(network)
@@ -77,17 +78,20 @@ func AutoLogin(client nodepb.NodeServiceClient, network string) error {
 	return err
 }
 
+// StoreSecret - stores auth secret locally
 func StoreSecret(key string, network string) error {
 	d1 := []byte(key)
 	err := ioutil.WriteFile(ncutils.GetNetclientPathSpecific()+"secret-"+network, d1, 0644)
 	return err
 }
 
+// RetrieveSecret - fetches secret locally
 func RetrieveSecret(network string) (string, error) {
 	dat, err := ioutil.ReadFile(ncutils.GetNetclientPathSpecific() + "secret-" + network)
 	return string(dat), err
 }
 
+// Configuraion - struct for mac and pass
 type Configuration struct {
 	MacAddress string
 	Password   string

+ 4 - 4
netclient/command/commands.go

@@ -25,8 +25,8 @@ var (
 
 func Join(cfg config.ClientConfig, privateKey string) error {
 
-	err := functions.JoinNetwork(cfg, privateKey)
-
+	var err error
+	err = functions.JoinNetwork(cfg, privateKey)
 	if err != nil && !cfg.DebugJoin {
 		if !strings.Contains(err.Error(), "ALREADY_INSTALLED") {
 			ncutils.PrintLog("error installing: "+err.Error(), 1)
@@ -39,7 +39,7 @@ func Join(cfg config.ClientConfig, privateKey string) error {
 			}
 			if cfg.Daemon != "off" {
 				if ncutils.IsLinux() {
-					err = daemon.RemoveSystemDServices(cfg.Network)
+					err = daemon.RemoveSystemDServices()
 				}
 				if err != nil {
 					ncutils.PrintLog("error removing services: "+err.Error(), 1)
@@ -189,7 +189,7 @@ func Pull(cfg config.ClientConfig) error {
 }
 
 func List(cfg config.ClientConfig) error {
-	err := functions.List()
+	err := functions.List(cfg.Network)
 	return err
 }
 

+ 20 - 9
netclient/config/config.go

@@ -15,11 +15,13 @@ import (
 	"gopkg.in/yaml.v3"
 )
 
+// GlobalConfig - struct for handling IntClients currently
 type GlobalConfig struct {
 	GRPCWireGuard string `yaml:"grpcwg"`
 	Client        models.IntClient
 }
 
+// ClientConfig - struct for dealing with client configuration
 type ClientConfig struct {
 	Server          ServerConfig `yaml:"server"`
 	Node            models.Node  `yaml:"node"`
@@ -28,23 +30,25 @@ type ClientConfig struct {
 	OperatingSystem string       `yaml:"operatingsystem"`
 	DebugJoin       bool         `yaml:"debugjoin"`
 }
+
+// ServerConfig - struct for dealing with the server information for a netclient
 type ServerConfig struct {
-	CoreDNSAddr   string `yaml:"corednsaddr"`
-	GRPCAddress   string `yaml:"grpcaddress"`
-	APIAddress    string `yaml:"apiaddress"`
-	AccessKey     string `yaml:"accesskey"`
-	GRPCSSL       string `yaml:"grpcssl"`
-	GRPCWireGuard string `yaml:"grpcwg"`
+	CoreDNSAddr     string `yaml:"corednsaddr"`
+	GRPCAddress     string `yaml:"grpcaddress"`
+	APIAddress      string `yaml:"apiaddress"`
+	AccessKey       string `yaml:"accesskey"`
+	GRPCSSL         string `yaml:"grpcssl"`
+	GRPCWireGuard   string `yaml:"grpcwg"`
 	CheckinInterval string `yaml:"checkininterval"`
 }
 
-//reading in the env file
+// Write - writes the config of a client to disk
 func Write(config *ClientConfig, network string) error {
 	if network == "" {
 		err := errors.New("no network provided - exiting")
 		return err
 	}
-	_, err := os.Stat(ncutils.GetNetclientPath()+"/config")
+	_, err := os.Stat(ncutils.GetNetclientPath() + "/config")
 	if os.IsNotExist(err) {
 		os.MkdirAll(ncutils.GetNetclientPath()+"/config", 0744)
 	} else if err != nil {
@@ -66,6 +70,7 @@ func Write(config *ClientConfig, network string) error {
 	return err
 }
 
+// WriteServer - writes the config of a server to disk for client
 func WriteServer(server string, accesskey string, network string) error {
 	if network == "" {
 		err := errors.New("no network provided - exiting")
@@ -73,7 +78,7 @@ func WriteServer(server string, accesskey string, network string) error {
 	}
 	nofile := false
 	//home, err := homedir.Dir()
-	_, err := os.Stat(ncutils.GetNetclientPath()+"/config")
+	_, err := os.Stat(ncutils.GetNetclientPath() + "/config")
 	if os.IsNotExist(err) {
 		os.MkdirAll(ncutils.GetNetclientPath()+"/config", 0744)
 	} else if err != nil {
@@ -149,6 +154,7 @@ func WriteServer(server string, accesskey string, network string) error {
 	return err
 }
 
+// ClientConfig.ReadConfig - used to read config from client disk into memory
 func (config *ClientConfig) ReadConfig() {
 
 	nofile := false
@@ -181,6 +187,7 @@ func (config *ClientConfig) ReadConfig() {
 	}
 }
 
+// ModConfig - overwrites the node inside client config on disk
 func ModConfig(node *models.Node) error {
 	network := node.Network
 	if network == "" {
@@ -201,6 +208,7 @@ func ModConfig(node *models.Node) error {
 	return err
 }
 
+// GetCLIConfig - gets the cli flags as a config
 func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 	var cfg ClientConfig
 	if c.String("token") != "" {
@@ -312,6 +320,7 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 	return cfg, privateKey, nil
 }
 
+// ReadConfig - reads a config of a client from disk for specified network
 func ReadConfig(network string) (*ClientConfig, error) {
 	if network == "" {
 		err := errors.New("no network provided - exiting")
@@ -340,6 +349,7 @@ func ReadConfig(network string) (*ClientConfig, error) {
 	return &cfg, err
 }
 
+// FileExists - checks if a file exists on disk
 func FileExists(f string) bool {
 	info, err := os.Stat(f)
 	if os.IsNotExist(err) {
@@ -348,6 +358,7 @@ func FileExists(f string) bool {
 	return !info.IsDir()
 }
 
+// GetNode - parses a network specified client config for node data
 func GetNode(network string) models.Node {
 
 	modcfg, err := ReadConfig(network)

+ 3 - 3
netclient/daemon/macos.go

@@ -1,12 +1,12 @@
 package daemon
 
 import (
+	"fmt"
+	"github.com/gravitl/netmaker/netclient/ncutils"
 	"io/ioutil"
 	"log"
 	"os"
-	"fmt"
 	"path/filepath"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
 const MAC_SERVICE_NAME = "com.gravitl.netclient"
@@ -93,7 +93,7 @@ func MacDaemonString(interval string) string {
 		</dict>
 </dict>
 </plist>
-`,interval)
+`, interval)
 }
 
 type MacTemplateData struct {

+ 28 - 32
netclient/daemon/systemd.go

@@ -11,8 +11,9 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
+// SetupSystemDDaemon - sets system daemon for supported machines
 func SetupSystemDDaemon(interval string) error {
-	
+
 	if ncutils.IsWindows() {
 		return nil
 	}
@@ -96,51 +97,46 @@ WantedBy=timers.target
 	return nil
 }
 
-func RemoveSystemDServices(network string) error {
+func CleanupLinux() {
+	err := os.RemoveAll(ncutils.GetNetclientPath())
+	if err != nil {
+		ncutils.PrintLog("Removing netclient binary: "+err.Error(), 1)
+	}
+}
+
+// RemoveSystemDServices - removes the systemd services on a machine
+func RemoveSystemDServices() error {
 	//sysExec, err := exec.LookPath("systemctl")
-	if !ncutils.IsWindows() {
-		fullremove, err := isOnlyService(network)
+	var err error
+	if !ncutils.IsWindows() && isOnlyService() {
 		if err != nil {
 			log.Println(err)
 		}
-
-		if fullremove {
-			_, err = ncutils.RunCmd("systemctl disable netclient.service", true)
-		}
-		_, _ = ncutils.RunCmd("systemctl daemon-reload", true)
-
-		if ncutils.FileExists("/etc/systemd/system/netclient.timer") {
-			_, _ = ncutils.RunCmd("systemctl disable netclient.timer", true)
-		}
-		if fullremove {
-			if ncutils.FileExists("/etc/systemd/system/netclient.service") {
-				err = os.Remove("/etc/systemd/system/netclient.service")
+		ncutils.RunCmd("systemctl disable netclient.service", false)
+		ncutils.RunCmd("systemctl disable netclient.timer", false)
+		if ncutils.FileExists("/etc/systemd/system/netclient.service") {
+			err = os.Remove("/etc/systemd/system/netclient.service")
+			if err != nil {
+				ncutils.Log("Error removing /etc/systemd/system/netclient.service. Please investigate.")
 			}
 		}
 		if ncutils.FileExists("/etc/systemd/system/netclient.timer") {
 			err = os.Remove("/etc/systemd/system/netclient.timer")
+			if err != nil {
+				ncutils.Log("Error removing /etc/systemd/system/netclient.timer. Please investigate.")
+			}
 		}
-		if err != nil {
-			log.Println("Error removing file. Please investigate.")
-			log.Println(err)
-		}
-		_, _ = ncutils.RunCmd("systemctl daemon-reload", true)
-		_, _ = ncutils.RunCmd("systemctl reset-failed", true)
+		ncutils.RunCmd("systemctl daemon-reload", false)
+		ncutils.RunCmd("systemctl reset-failed", false)
+		ncutils.Log("removed systemd remnants if any existed")
 	}
 	return nil
 }
 
-
-func isOnlyService(network string) (bool, error) {
-	isonly := false
+func isOnlyService() bool {
 	files, err := filepath.Glob("/etc/netclient/config/netconfig-*")
 	if err != nil {
-		return isonly, err
+		return false
 	}
-	count := len(files)
-	if count == 0 {
-		isonly = true
-	}
-	return isonly, err
-
+	return len(files) == 0
 }

+ 5 - 0
netclient/daemon/windows.go

@@ -12,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
+// SetupWindowsDaemon - sets up the Windows daemon service
 func SetupWindowsDaemon() error {
 
 	if !ncutils.FileExists(ncutils.GetNetclientPathSpecific() + "winsw.xml") {
@@ -42,6 +43,7 @@ func SetupWindowsDaemon() error {
 	return nil
 }
 
+// CleanupWindows - cleans up windows files
 func CleanupWindows() {
 	if !ncutils.FileExists(ncutils.GetNetclientPathSpecific() + "winsw.xml") {
 		writeServiceConfig()
@@ -73,12 +75,15 @@ func writeServiceConfig() error {
 }
 
 // == Daemon ==
+
+// StopWindowsDaemon - stops the Windows daemon
 func StopWindowsDaemon() {
 	ncutils.Log("no networks detected, stopping Windows, Netclient daemon")
 	// stop daemon, will not overwrite
 	ncutils.RunCmd(strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)+`winsw.exe stop`, true)
 }
 
+// RemoveWindowsDaemon - removes the Windows daemon
 func RemoveWindowsDaemon() {
 	// uninstall daemon, will not restart or start another
 	ncutils.RunCmd(strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)+`winsw.exe uninstall`, true)

+ 42 - 54
netclient/functions/checkin.go

@@ -9,7 +9,6 @@ import (
 	"strings"
 
 	nodepb "github.com/gravitl/netmaker/grpc"
-	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
@@ -111,6 +110,7 @@ func checkNodeActions(node *models.Node, networkName string, servercfg config.Se
 	return ""
 }
 
+// CheckConfig - checks if current config of client needs update, see flow below
 /**
  * Pull changes if any (interface refresh)
  * - Save it
@@ -148,10 +148,7 @@ func CheckConfig(cliconf config.ClientConfig) error {
 	return Push(network)
 }
 
-/**
- * Pull the latest node from server
- * Perform action if necessary
- */
+// Pull - pulls the latest config from the server, if manual it will overwrite
 func Pull(network string, manual bool) (*models.Node, error) {
 	cfg, err := config.ReadConfig(network)
 	node := cfg.Node
@@ -199,11 +196,6 @@ func Pull(network string, manual bool) (*models.Node, error) {
 		if err = json.Unmarshal([]byte(readres.Data), &resNode); err != nil {
 			return nil, err
 		}
-	} else { // handle server side read
-		resNode, err = logic.GetNode(node.MacAddress, node.Network)
-		if err != nil && !ncutils.IsEmptyRecord(err) {
-			return nil, err
-		}
 	}
 	// ensure that the OS never changes
 	resNode.OS = runtime.GOOS
@@ -259,7 +251,9 @@ func Pull(network string, manual bool) (*models.Node, error) {
 	return &resNode, err
 }
 
+// Push - pushes current client configuration to server
 func Push(network string) error {
+
 	cfg, err := config.ReadConfig(network)
 	if err != nil {
 		return err
@@ -269,59 +263,53 @@ func Push(network string) error {
 	postnode.OS = runtime.GOOS
 	postnode.SetLastCheckIn()
 
-	if postnode.IsServer != "yes" { // handle client side
-		var header metadata.MD
-		var wcclient nodepb.NodeServiceClient
-		conn, err := grpc.Dial(cfg.Server.GRPCAddress,
-			ncutils.GRPCRequestOpts(cfg.Server.GRPCSSL))
-		if err != nil {
-			ncutils.PrintLog("Cant dial GRPC server: "+err.Error(), 1)
-			return err
-		}
-		defer conn.Close()
-		wcclient = nodepb.NewNodeServiceClient(conn)
-
-		ctx, err := auth.SetJWT(wcclient, network)
-		if err != nil {
-			ncutils.PrintLog("Failed to authenticate with server: "+err.Error(), 1)
-			return err
-		}
-		if postnode.IsPending != "yes" {
-			privateKey, err := wireguard.RetrievePrivKey(network)
-			if err != nil {
-				return err
-			}
-			privateKeyWG, err := wgtypes.ParseKey(privateKey)
-			if err != nil {
-				return err
-			}
-			if postnode.PublicKey != privateKeyWG.PublicKey().String() {
-				postnode.PublicKey = privateKeyWG.PublicKey().String()
-			}
-		}
-		nodeData, err := json.Marshal(&postnode)
-		if err != nil {
-			return err
-		}
+	var header metadata.MD
+	var wcclient nodepb.NodeServiceClient
+	conn, err := grpc.Dial(cfg.Server.GRPCAddress,
+		ncutils.GRPCRequestOpts(cfg.Server.GRPCSSL))
+	if err != nil {
+		ncutils.PrintLog("Cant dial GRPC server: "+err.Error(), 1)
+		return err
+	}
+	defer conn.Close()
+	wcclient = nodepb.NewNodeServiceClient(conn)
 
-		req := &nodepb.Object{
-			Data:     string(nodeData),
-			Type:     nodepb.NODE_TYPE,
-			Metadata: "",
-		}
-		data, err := wcclient.UpdateNode(ctx, req, grpc.Header(&header))
+	ctx, err := auth.SetJWT(wcclient, network)
+	if err != nil {
+		ncutils.PrintLog("Failed to authenticate with server: "+err.Error(), 1)
+		return err
+	}
+	if postnode.IsPending != "yes" {
+		privateKey, err := wireguard.RetrievePrivKey(network)
 		if err != nil {
 			return err
 		}
-		err = json.Unmarshal([]byte(data.Data), &postnode)
+		privateKeyWG, err := wgtypes.ParseKey(privateKey)
 		if err != nil {
 			return err
 		}
-	} else {
-		if err = postnode.Update(&postnode); err != nil {
-			return err
+		if postnode.PublicKey != privateKeyWG.PublicKey().String() {
+			postnode.PublicKey = privateKeyWG.PublicKey().String()
 		}
 	}
+	nodeData, err := json.Marshal(&postnode)
+	if err != nil {
+		return err
+	}
+
+	req := &nodepb.Object{
+		Data:     string(nodeData),
+		Type:     nodepb.NODE_TYPE,
+		Metadata: "",
+	}
+	data, err := wcclient.UpdateNode(ctx, req, grpc.Header(&header))
+	if err != nil {
+		return err
+	}
+	err = json.Unmarshal([]byte(data.Data), &postnode)
+	if err != nil {
+		return err
+	}
 	err = config.ModConfig(&postnode)
 	return err
 }

+ 10 - 34
netclient/functions/common.go

@@ -12,7 +12,6 @@ import (
 	"strings"
 
 	nodepb "github.com/gravitl/netmaker/grpc"
-	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
@@ -28,6 +27,7 @@ var (
 	wcclient nodepb.NodeServiceClient
 )
 
+// ListPorts - lists ports of WireGuard devices
 func ListPorts() error {
 	wgclient, err := wgctrl.New()
 	if err != nil {
@@ -127,6 +127,7 @@ func needInterfaceUpdate(ctx context.Context, mac string, network string, iface
 	return iface != oldiface, oldiface, err
 }
 
+// GetNode - gets node locally
 func GetNode(network string) models.Node {
 
 	modcfg, err := config.ReadConfig(network)
@@ -137,6 +138,7 @@ func GetNode(network string) models.Node {
 	return modcfg.Node
 }
 
+// Uninstall - uninstalls networks from client
 func Uninstall() error {
 	networks, err := ncutils.GetSystemNetworks()
 	if err != nil {
@@ -155,6 +157,8 @@ func Uninstall() error {
 		daemon.CleanupWindows()
 	} else if ncutils.IsMac() {
 		daemon.CleanupMac()
+	} else if ncutils.IsLinux() {
+		daemon.CleanupLinux()
 	} else if !ncutils.IsKernel() {
 		ncutils.PrintLog("manual cleanup required", 1)
 	}
@@ -162,6 +166,7 @@ func Uninstall() error {
 	return err
 }
 
+// LeaveNetwork - client exits a network
 func LeaveNetwork(network string) error {
 	cfg, err := config.ReadConfig(network)
 	if err != nil {
@@ -200,17 +205,11 @@ func LeaveNetwork(network string) error {
 				ncutils.PrintLog("removed machine from "+node.Network+" network on remote server", 1)
 			}
 		}
-	} else { // handle server side
-		node.SetID()
-		if err = logic.DeleteNode(node.ID, true); err != nil {
-			ncutils.PrintLog("error removing server on network "+node.Network, 1)
-		} else {
-			ncutils.PrintLog("removed netmaker server instance on  "+node.Network, 1)
-		}
 	}
 	return RemoveLocalInstance(cfg, network)
 }
 
+// RemoveLocalInstance - remove all netclient files locally for a network
 func RemoveLocalInstance(cfg *config.ClientConfig, networkName string) error {
 	err := WipeLocal(networkName)
 	if err != nil {
@@ -224,12 +223,13 @@ func RemoveLocalInstance(cfg *config.ClientConfig, networkName string) error {
 		} else if ncutils.IsMac() {
 			//TODO: Delete mac daemon
 		} else {
-			err = daemon.RemoveSystemDServices(networkName)
+			err = daemon.RemoveSystemDServices()
 		}
 	}
 	return err
 }
 
+// DeleteInterface - delete an interface of a network
 func DeleteInterface(ifacename string, postdown string) error {
 	var err error
 	if !ncutils.IsKernel() {
@@ -249,31 +249,7 @@ func DeleteInterface(ifacename string, postdown string) error {
 	return err
 }
 
-func List() error {
-
-	networks, err := ncutils.GetSystemNetworks()
-	if err != nil {
-		return err
-	}
-	for _, network := range networks {
-		cfg, err := config.ReadConfig(network)
-		if err == nil {
-			jsoncfg, _ := json.Marshal(
-				map[string]string{
-					"Name":           cfg.Node.Name,
-					"Interface":      cfg.Node.Interface,
-					"PrivateIPv4":    cfg.Node.Address,
-					"PrivateIPv6":    cfg.Node.Address6,
-					"PublicEndpoint": cfg.Node.Endpoint,
-				})
-			fmt.Println(network + ": " + string(jsoncfg))
-		} else {
-			ncutils.PrintLog(network+": Could not retrieve network configuration.", 1)
-		}
-	}
-	return nil
-}
-
+// WipeLocal - wipes local instance
 func WipeLocal(network string) error {
 	cfg, err := config.ReadConfig(network)
 	if err != nil {

+ 46 - 52
netclient/functions/join.go

@@ -6,9 +6,8 @@ import (
 	"errors"
 	"fmt"
 	"log"
-
+	"os/exec"
 	nodepb "github.com/gravitl/netmaker/grpc"
-	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
@@ -21,31 +20,33 @@ import (
 	"google.golang.org/grpc"
 )
 
+// JoinNetwork - helps a client join a network
 func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 
-	hasnet := local.HasNetwork(cfg.Network)
-	if hasnet {
-		err := errors.New("ALREADY_INSTALLED. Netclient appears to already be installed for " + cfg.Network + ". To re-install, please remove by executing 'sudo netclient leave -n " + cfg.Network + "'. Then re-run the install command.")
-		return err
-	}
-
-	err := config.Write(&cfg, cfg.Network)
-	if err != nil {
-		return err
-	}
-
 	if cfg.Node.Network == "" {
 		return errors.New("no network provided")
 	}
 
+	var err error
+	if cfg.Node.IsServer != "yes" {
+		if local.HasNetwork(cfg.Network) {
+			err := errors.New("ALREADY_INSTALLED. Netclient appears to already be installed for " + cfg.Network + ". To re-install, please remove by executing 'sudo netclient leave -n " + cfg.Network + "'. Then re-run the install command.")
+			return err
+		}
+		err = config.Write(&cfg, cfg.Network)
+		if err != nil {
+			return err
+		}
+		if cfg.Node.Password == "" {
+			cfg.Node.Password = ncutils.GenPass()
+		}
+		auth.StoreSecret(cfg.Node.Password, cfg.Node.Network)
+	}
+
 	if cfg.Node.LocalRange != "" && cfg.Node.LocalAddress == "" {
 		log.Println("local vpn, getting local address from range: " + cfg.Node.LocalRange)
 		cfg.Node.LocalAddress = getLocalIP(cfg.Node)
 	}
-	if cfg.Node.Password == "" {
-		cfg.Node.Password = ncutils.GenPass()
-	}
-	auth.StoreSecret(cfg.Node.Password, cfg.Node.Network)
 
 	// set endpoint if blank. set to local if local net, retrieve from function if not
 	if cfg.Node.Endpoint == "" {
@@ -80,6 +81,14 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 			cfg.Node.MacAddress = macs[0]
 		}
 	}
+	if ncutils.IsLinux() {
+		_, err := exec.LookPath("resolvectl")
+		if err != nil {
+			ncutils.PrintLog("resolvectl not present",2)
+			ncutils.PrintLog("unable to configure DNS automatically, disabling automated DNS management",2)
+			cfg.Node.DNSOn = "no"
+		}
+	}
 
 	// differentiate between client/server here
 	var node models.Node // fill this node with appropriate calls
@@ -139,19 +148,6 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		if err = json.Unmarshal([]byte(nodeData), &node); err != nil {
 			return err
 		}
-	} else { // handle server side node creation
-		ncutils.Log("adding a server instance on network " + postnode.Network)
-		if err = config.ModConfig(postnode); err != nil {
-			return err
-		}
-		node, err = logic.CreateNode(*postnode, cfg.Network)
-		if err != nil {
-			return err
-		}
-		err = logic.SetNetworkNodesLastModified(node.Network)
-		if err != nil {
-			return err
-		}
 	}
 
 	// get free port based on returned default listen port
@@ -168,28 +164,26 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		}
 		node.Endpoint = node.LocalAddress
 	}
-
-	err = config.ModConfig(&node)
-	if err != nil {
-		return err
-	}
-
-	err = wireguard.StorePrivKey(privateKey, cfg.Network)
-	if err != nil {
-		return err
-	}
-
-	// pushing any local changes to server before starting wireguard
-	err = Push(cfg.Network)
-	if err != nil {
-		return err
-	}
-
-	if node.IsPending == "yes" {
-		ncutils.Log("Node is marked as PENDING.")
-		ncutils.Log("Awaiting approval from Admin before configuring WireGuard.")
-		if cfg.Daemon != "off" {
-			return daemon.InstallDaemon(cfg)
+	if node.IsServer != "yes" { // == handle client side ==
+		err = config.ModConfig(&node)
+		if err != nil {
+			return err
+		}
+		err = wireguard.StorePrivKey(privateKey, cfg.Network)
+		if err != nil {
+			return err
+		}
+		if node.IsPending == "yes" {
+			ncutils.Log("Node is marked as PENDING.")
+			ncutils.Log("Awaiting approval from Admin before configuring WireGuard.")
+			if cfg.Daemon != "off" {
+				return daemon.InstallDaemon(cfg)
+			}
+		}
+		// pushing any local changes to server before starting wireguard
+		err = Push(cfg.Network)
+		if err != nil {
+			return err
 		}
 	}
 

+ 128 - 0
netclient/functions/list.go

@@ -0,0 +1,128 @@
+package functions
+
+import (
+	"encoding/json"
+	"fmt"
+
+	nodepb "github.com/gravitl/netmaker/grpc"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/auth"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/metadata"
+)
+
+type Peer struct {
+	Name           string `json:"name"`
+	Interface      string `json:"interface,omitempty"`
+	PrivateIPv4    string `json:"private_ipv4,omitempty"`
+	PrivateIPv6    string `json:"private_ipv6,omitempty"`
+	PublicEndpoint string `json:"public_endoint,omitempty"`
+}
+
+type Network struct {
+	Name        string `json:"name"`
+	CurrentNode Peer   `json:"current_node"`
+	Peers       []Peer `json:"peers"`
+}
+
+func List(network string) error {
+	nets := []Network{}
+	var err error
+	var networks []string
+	if network == "all" {
+		networks, err = ncutils.GetSystemNetworks()
+		if err != nil {
+			return err
+		}
+	} else {
+		networks = append(networks, network)
+	}
+
+	for _, network := range networks {
+		net, err := getNetwork(network)
+		if err != nil {
+			ncutils.PrintLog(network+": Could not retrieve network configuration.", 1)
+			return err
+		}
+		nets = append(nets, net)
+	}
+
+	jsoncfg, _ := json.Marshal(struct {
+		Networks []Network `json:"networks"`
+	}{nets})
+	fmt.Println(string(jsoncfg))
+
+	return nil
+}
+
+func getNetwork(network string) (Network, error) {
+	cfg, err := config.ReadConfig(network)
+	if err != nil {
+		return Network{}, fmt.Errorf("reading configuration for network %v: %w", network, err)
+	}
+	peers, err := getPeers(network)
+	if err != nil {
+		return Network{}, fmt.Errorf("listing peers for network %v: %w", network, err)
+	}
+	return Network{
+		Name:  network,
+		Peers: peers,
+		CurrentNode: Peer{
+			Name:           cfg.Node.Name,
+			Interface:      cfg.Node.Interface,
+			PrivateIPv4:    cfg.Node.Address,
+			PrivateIPv6:    cfg.Node.Address6,
+			PublicEndpoint: cfg.Node.Endpoint,
+		},
+	}, nil
+}
+
+func getPeers(network string) ([]Peer, error) {
+	cfg, err := config.ReadConfig(network)
+	if err != nil {
+		return []Peer{}, err
+	}
+	nodecfg := cfg.Node
+	var nodes []models.Node
+
+	var wcclient nodepb.NodeServiceClient
+	conn, err := grpc.Dial(cfg.Server.GRPCAddress,
+		ncutils.GRPCRequestOpts(cfg.Server.GRPCSSL))
+
+	if err != nil {
+		return []Peer{}, fmt.Errorf("connecting to %v: %w", cfg.Server.GRPCAddress, err)
+	}
+	defer conn.Close()
+	// Instantiate the BlogServiceClient with our client connection to the server
+	wcclient = nodepb.NewNodeServiceClient(conn)
+
+	req := &nodepb.Object{
+		Data: nodecfg.MacAddress + "###" + nodecfg.Network,
+		Type: nodepb.STRING_TYPE,
+	}
+
+	ctx, err := auth.SetJWT(wcclient, network)
+	if err != nil {
+		return []Peer{}, fmt.Errorf("authenticating: %w", err)
+	}
+	var header metadata.MD
+
+	response, err := wcclient.GetPeers(ctx, req, grpc.Header(&header))
+	if err != nil {
+		return []Peer{}, fmt.Errorf("retrieving peers: %w", err)
+	}
+	if err := json.Unmarshal([]byte(response.GetData()), &nodes); err != nil {
+		return []Peer{}, fmt.Errorf("unmarshaling data for peers: %w", err)
+	}
+
+	peers := []Peer{}
+	for _, node := range nodes {
+		if node.Name != cfg.Node.Name {
+			peers = append(peers, Peer{Name: fmt.Sprintf("%v.%v", node.Name, network), PrivateIPv4: node.Address, PrivateIPv6: node.Address6})
+		}
+	}
+
+	return peers, nil
+}

+ 2 - 0
netclient/local/dns.go

@@ -12,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
+// SetDNS - sets the DNS of a local machine
 func SetDNS(nameserver string) error {
 	bytes, err := ioutil.ReadFile("/etc/resolv.conf")
 	if err != nil {
@@ -33,6 +34,7 @@ func SetDNS(nameserver string) error {
 	return err
 }
 
+// UpdateDNS - updates local DNS of client
 func UpdateDNS(ifacename string, network string, nameserver string) error {
 	if ncutils.IsWindows() {
 		return nil

+ 10 - 3
netclient/local/local.go

@@ -5,13 +5,15 @@ import (
 	"errors"
 	"log"
 	"net"
+	"os"
+	"os/exec"
 	"runtime"
 	"strings"
-	"os/exec"
-	"os"
+
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
+// SetIPForwarding - Sets IP forwarding if it's mac or linux
 func SetIPForwarding() error {
 	os := runtime.GOOS
 	var err error
@@ -26,6 +28,7 @@ func SetIPForwarding() error {
 	return err
 }
 
+// SetIPForwardingLinux - sets the ipforwarding for linux
 func SetIPForwardingLinux() error {
 	out, err := ncutils.RunCmd("sysctl net.ipv4.ip_forward", true)
 	if err != nil {
@@ -44,6 +47,7 @@ func SetIPForwardingLinux() error {
 	return nil
 }
 
+// SetIPForwardingMac - sets ip forwarding for mac
 func SetIPForwardingMac() error {
 	_, err := ncutils.RunCmd("sysctl -w net.inet.ip.forwarding=1", true)
 	if err != nil {
@@ -52,6 +56,7 @@ func SetIPForwardingMac() error {
 	return err
 }
 
+// IsWGInstalled - checks if WireGuard is installed
 func IsWGInstalled() bool {
 	out, err := ncutils.RunCmd("wg help", true)
 	if err != nil {
@@ -61,6 +66,7 @@ func IsWGInstalled() bool {
 	return strings.Contains(out, "Available subcommand")
 }
 
+// GetMacIface - gets mac interface
 func GetMacIface(ipstring string) (string, error) {
 	var wgiface string
 	_, checknet, err := net.ParseCIDR(ipstring + "/24")
@@ -90,6 +96,7 @@ func GetMacIface(ipstring string) (string, error) {
 	return wgiface, err
 }
 
+// HasNetwork - checks if a network exists locally
 func HasNetwork(network string) bool {
-		return ncutils.FileExists(ncutils.GetNetclientPathSpecific() + "netconfig-" + network)
+	return ncutils.FileExists(ncutils.GetNetclientPathSpecific() + "netconfig-" + network)
 }

+ 7 - 2
netclient/main.go

@@ -24,7 +24,12 @@ func main() {
 	app := cli.NewApp()
 	app.Name = "Netclient CLI"
 	app.Usage = "Netmaker's netclient agent and CLI. Used to perform interactions with Netmaker server and set local WireGuard config."
-	app.Version = "v0.8.3"
+	app.Version = "v0.8.4"
+
+	hostname, err := os.Hostname()
+	if err != nil {
+		hostname = ""
+	}
 
 	cliFlags := []cli.Flag{
 		&cli.StringFlag{
@@ -91,7 +96,7 @@ func main() {
 		&cli.StringFlag{
 			Name:    "name",
 			EnvVars: []string{"NETCLIENT_NAME"},
-			Value:   "",
+			Value:   hostname,
 			Usage:   "Identifiable name for machine within Netmaker network.",
 		},
 		&cli.StringFlag{

+ 45 - 5
netclient/ncutils/netclientutils.go

@@ -23,31 +23,49 @@ import (
 	"google.golang.org/grpc/credentials"
 )
 
+// NO_DB_RECORD - error message result
 const NO_DB_RECORD = "no result found"
+
+// NO_DB_RECORDS - error record result
 const NO_DB_RECORDS = "could not find any records"
+
+// LINUX_APP_DATA_PATH - linux path
 const LINUX_APP_DATA_PATH = "/etc/netclient"
+
+// WINDOWS_APP_DATA_PATH - windows path
 const WINDOWS_APP_DATA_PATH = "C:\\ProgramData\\Netclient"
+
+// WINDOWS_SVC_NAME - service name
 const WINDOWS_SVC_NAME = "netclient"
+
+// NETCLIENT_DEFAULT_PORT - default port
 const NETCLIENT_DEFAULT_PORT = 51821
+
+// DEFAULT_GC_PERCENT - garbage collection percent
 const DEFAULT_GC_PERCENT = 10
 
+// Log - logs a message
 func Log(message string) {
 	log.SetFlags(log.Flags() &^ (log.Llongfile | log.Lshortfile))
 	log.Println("[netclient]", message)
 }
 
+// IsWindows - checks if is windows
 func IsWindows() bool {
 	return runtime.GOOS == "windows"
 }
 
+// IsMac - checks if is a mac
 func IsMac() bool {
 	return runtime.GOOS == "darwin"
 }
 
+// IsLinux - checks if is linux
 func IsLinux() bool {
 	return runtime.GOOS == "linux"
 }
 
+// GetWireGuard - checks if wg is installed
 func GetWireGuard() string {
 	userspace := os.Getenv("WG_QUICK_USERSPACE_IMPLEMENTATION")
 	if userspace != "" && (userspace == "boringtun" || userspace == "wireguard-go") {
@@ -56,6 +74,7 @@ func GetWireGuard() string {
 	return "wg"
 }
 
+// IsKernel - checks if running kernel WireGuard
 func IsKernel() bool {
 	//TODO
 	//Replace && true with some config file value
@@ -63,7 +82,7 @@ func IsKernel() bool {
 	return IsLinux() && os.Getenv("WG_QUICK_USERSPACE_IMPLEMENTATION") == ""
 }
 
-// == database returned nothing error ==
+// IsEmptyRecord - repeat from database
 func IsEmptyRecord(err error) bool {
 	if err == nil {
 		return false
@@ -72,6 +91,7 @@ func IsEmptyRecord(err error) bool {
 }
 
 //generate an access key value
+// GenPass - generates a pass
 func GenPass() string {
 
 	var seededRand *rand.Rand = rand.New(
@@ -87,6 +107,7 @@ func GenPass() string {
 	return string(b)
 }
 
+// GetPublicIP - gets public ip
 func GetPublicIP() (string, error) {
 
 	iplist := []string{"http://ip.client.gravitl.com", "https://ifconfig.me", "http://api.ipify.org", "http://ipinfo.io/ip"}
@@ -113,6 +134,7 @@ func GetPublicIP() (string, error) {
 	return endpoint, err
 }
 
+// GetMacAddr - get's mac address
 func GetMacAddr() ([]string, error) {
 	ifas, err := net.Interfaces()
 	if err != nil {
@@ -133,7 +155,12 @@ func parsePeers(keepalive int32, peers []wgtypes.PeerConfig) (string, error) {
 	if keepalive <= 0 {
 		keepalive = 20
 	}
+	
 	for _, peer := range peers {
+		endpointString :=  ""
+		if peer.Endpoint != nil && peer.Endpoint.String() != "" {
+			endpointString += "Endpoint = " + peer.Endpoint.String()
+		}
 		newAllowedIps := []string{}
 		for _, allowedIP := range peer.AllowedIPs {
 			newAllowedIps = append(newAllowedIps, allowedIP.String())
@@ -141,19 +168,20 @@ func parsePeers(keepalive int32, peers []wgtypes.PeerConfig) (string, error) {
 		peersString += fmt.Sprintf(`[Peer]
 PublicKey = %s
 AllowedIps = %s
-Endpoint = %s
 PersistentKeepAlive = %s
+%s
 
 `,
 			peer.PublicKey.String(),
 			strings.Join(newAllowedIps, ","),
-			peer.Endpoint.String(),
 			strconv.Itoa(int(keepalive)),
+			endpointString,
 		)
 	}
 	return peersString, nil
 }
 
+// CreateUserSpaceConf - creates a user space WireGuard conf
 func CreateUserSpaceConf(address string, privatekey string, listenPort string, mtu int32, perskeepalive int32, peers []wgtypes.PeerConfig) (string, error) {
 	peersString, err := parsePeers(perskeepalive, peers)
 	listenPortString := ""
@@ -183,6 +211,7 @@ MTU = %s
 	return config, nil
 }
 
+// GetLocalIP - gets local ip of machine
 func GetLocalIP(localrange string) (string, error) {
 	_, localRange, err := net.ParseCIDR(localrange)
 	if err != nil {
@@ -229,6 +258,7 @@ func GetLocalIP(localrange string) (string, error) {
 	return local, nil
 }
 
+// GetFreePort - gets free port of machine
 func GetFreePort(rangestart int32) (int32, error) {
 	if rangestart == 0 {
 		rangestart = NETCLIENT_DEFAULT_PORT
@@ -259,6 +289,7 @@ func GetFreePort(rangestart int32) (int32, error) {
 
 // == OS PATH FUNCTIONS ==
 
+// GetHomeDirWindows - gets home directory in windows
 func GetHomeDirWindows() string {
 	if IsWindows() {
 		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
@@ -270,6 +301,7 @@ func GetHomeDirWindows() string {
 	return os.Getenv("HOME")
 }
 
+// GetNetclientPath - gets netclient path locally
 func GetNetclientPath() string {
 	if IsWindows() {
 		return WINDOWS_APP_DATA_PATH
@@ -280,6 +312,7 @@ func GetNetclientPath() string {
 	}
 }
 
+// GetNetclientPathSpecific - gets specific netclient config path
 func GetNetclientPathSpecific() string {
 	if IsWindows() {
 		return WINDOWS_APP_DATA_PATH + "\\"
@@ -290,6 +323,7 @@ func GetNetclientPathSpecific() string {
 	}
 }
 
+// GRPCRequestOpts - gets grps request opts
 func GRPCRequestOpts(isSecure string) grpc.DialOption {
 	var requestOpts grpc.DialOption
 	requestOpts = grpc.WithInsecure()
@@ -300,6 +334,7 @@ func GRPCRequestOpts(isSecure string) grpc.DialOption {
 	return requestOpts
 }
 
+// Copy - copies a src file to dest
 func Copy(src, dst string) (int64, error) {
 	sourceFileStat, err := os.Stat(src)
 	if err != nil {
@@ -329,6 +364,7 @@ func Copy(src, dst string) (int64, error) {
 	return nBytes, err
 }
 
+// RunCmd - runs a local command
 func RunCmd(command string, printerr bool) (string, error) {
 	args := strings.Fields(command)
 	cmd := exec.Command(args[0], args[1:]...)
@@ -341,6 +377,7 @@ func RunCmd(command string, printerr bool) (string, error) {
 	return string(out), err
 }
 
+// RunsCmds - runs cmds
 func RunCmds(commands []string, printerr bool) error {
 	var err error
 	for _, command := range commands {
@@ -354,6 +391,7 @@ func RunCmds(commands []string, printerr bool) error {
 	return err
 }
 
+// FileExists - checks if file exists locally
 func FileExists(f string) bool {
 	info, err := os.Stat(f)
 	if os.IsNotExist(err) {
@@ -362,6 +400,7 @@ func FileExists(f string) bool {
 	return !info.IsDir()
 }
 
+// PrintLog - prints log
 func PrintLog(message string, loglevel int) {
 	log.SetFlags(log.Flags() &^ (log.Llongfile | log.Lshortfile))
 	if loglevel < 2 {
@@ -369,6 +408,7 @@ func PrintLog(message string, loglevel int) {
 	}
 }
 
+// GetSystemNetworks - get networks locally
 func GetSystemNetworks() ([]string, error) {
 	var networks []string
 	files, err := ioutil.ReadDir(GetNetclientPathSpecific())
@@ -394,5 +434,5 @@ func stringAfter(original string, substring string) string {
 	if adjustedPosition >= len(original) {
 		return ""
 	}
-	return original[adjustedPosition:len(original)]
-}
+	return original[adjustedPosition:]
+}

+ 1 - 1
netclient/ncwindows/windows.go

@@ -8,7 +8,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
-// Initialize windows directory & files and such
+// InitWindows - Initialize windows directory & files and such
 func InitWindows() {
 
 	_, directoryErr := os.Stat(ncutils.GetNetclientPath()) // Check if data directory exists or not

+ 33 - 91
netclient/server/grpc.go

@@ -9,7 +9,6 @@ import (
 	"time"
 
 	nodepb "github.com/gravitl/netmaker/grpc"
-	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
@@ -19,6 +18,7 @@ import (
 	"google.golang.org/grpc/metadata"
 )
 
+// RELAY_KEEPALIVE_MARKER - sets the relay keepalive marker
 const RELAY_KEEPALIVE_MARKER = "20007ms"
 
 func getGrpcClient(cfg *config.ClientConfig) (nodepb.NodeServiceClient, error) {
@@ -35,6 +35,7 @@ func getGrpcClient(cfg *config.ClientConfig) (nodepb.NodeServiceClient, error) {
 	return wcclient, nil
 }
 
+// CheckIn - checkin for node on a network
 func CheckIn(network string) (*models.Node, error) {
 	cfg, err := config.ReadConfig(network)
 	if err != nil {
@@ -71,72 +72,21 @@ func CheckIn(network string) (*models.Node, error) {
 	return &node, err
 }
 
-/*
-func RemoveNetwork(network string) error {
-	//need to  implement checkin on server side
-	cfg, err := config.ReadConfig(network)
-	if err != nil {
-		return err
-	}
-	servercfg := cfg.Server
-	node := cfg.Node
-	log.Println("Deleting remote node with MAC: " + node.MacAddress)
-
-	var wcclient nodepb.NodeServiceClient
-	conn, err := grpc.Dial(cfg.Server.GRPCAddress,
-		ncutils.GRPCRequestOpts(cfg.Server.GRPCSSL))
-	if err != nil {
-		log.Printf("Unable to establish client connection to "+servercfg.GRPCAddress+": %v", err)
-		//return err
-	} else {
-		wcclient = nodepb.NewNodeServiceClient(conn)
-		ctx, err := auth.SetJWT(wcclient, network)
-		if err != nil {
-			//return err
-			log.Printf("Failed to authenticate: %v", err)
-		} else {
-
-			var header metadata.MD
-
-			_, err = wcclient.DeleteNode(
-				ctx,
-				&nodepb.Object{
-					Data: node.MacAddress + "###" + node.Network,
-					Type: nodepb.STRING_TYPE,
-				},
-				grpc.Header(&header),
-			)
-			if err != nil {
-				log.Printf("Encountered error deleting node: %v", err)
-				log.Println(err)
-			} else {
-				log.Println("Deleted node " + node.MacAddress)
-			}
-		}
-	}
-	//err = functions.RemoveLocalInstance(network)
-
-	return err
-}
-*/
-
+// GetPeers - gets the peers for a node
 func GetPeers(macaddress string, network string, server string, dualstack bool, isIngressGateway bool, isServer bool) ([]wgtypes.PeerConfig, bool, []string, error) {
 	hasGateway := false
+	var err error
 	var gateways []string
 	var peers []wgtypes.PeerConfig
-	cfg, err := config.ReadConfig(network)
-	if err != nil {
-		log.Fatalf("Issue retrieving config for network: "+network+". Please investigate: %v", err)
-	}
-	nodecfg := cfg.Node
-	keepalive := nodecfg.PersistentKeepalive
-	keepalivedur, err := time.ParseDuration(strconv.FormatInt(int64(keepalive), 10) + "s")
-	keepaliveserver, err := time.ParseDuration(strconv.FormatInt(int64(5), 10) + "s")
-	if err != nil {
-		log.Fatalf("Issue with format of keepalive value. Please update netconfig: %v", err)
-	}
-	var nodes []models.Node // fill this either from server or client
-	if !isServer {          // set peers client side
+	var nodecfg models.Node
+	var nodes []models.Node // fill above fields from server or client
+
+	if !isServer { // set peers client side
+		cfg, err := config.ReadConfig(network)
+		if err != nil {
+			log.Fatalf("Issue retrieving config for network: "+network+". Please investigate: %v", err)
+		}
+		nodecfg = cfg.Node
 		var wcclient nodepb.NodeServiceClient
 		conn, err := grpc.Dial(cfg.Server.GRPCAddress,
 			ncutils.GRPCRequestOpts(cfg.Server.GRPCSSL))
@@ -170,11 +120,13 @@ func GetPeers(macaddress string, network string, server string, dualstack bool,
 			log.Println("Error unmarshaling data for peers")
 			return nil, hasGateway, gateways, err
 		}
-	} else { // set peers serverside
-		nodes, err = logic.GetPeers(nodecfg)
-		if err != nil {
-			return nil, hasGateway, gateways, err
-		}
+	}
+
+	keepalive := nodecfg.PersistentKeepalive
+	keepalivedur, err := time.ParseDuration(strconv.FormatInt(int64(keepalive), 10) + "s")
+	keepaliveserver, err := time.ParseDuration(strconv.FormatInt(int64(5), 10) + "s")
+	if err != nil {
+		log.Fatalf("Issue with format of keepalive value. Please update netconfig: %v", err)
 	}
 
 	for _, node := range nodes {
@@ -251,7 +203,7 @@ func GetPeers(macaddress string, network string, server string, dualstack bool,
 			}
 			allowedips = append(allowedips, addr6)
 		}
-		if nodecfg.IsServer == "yes" && !(node.IsServer == "yes"){
+		if nodecfg.IsServer == "yes" && !(node.IsServer == "yes") {
 			peer = wgtypes.PeerConfig{
 				PublicKey:                   pubkey,
 				PersistentKeepaliveInterval: &keepaliveserver,
@@ -292,16 +244,22 @@ func GetPeers(macaddress string, network string, server string, dualstack bool,
 	}
 	return peers, hasGateway, gateways, err
 }
+
+// GetExtPeers - gets the extpeers for a client
 func GetExtPeers(macaddress string, network string, server string, dualstack bool) ([]wgtypes.PeerConfig, error) {
 	var peers []wgtypes.PeerConfig
-
-	cfg, err := config.ReadConfig(network)
-	if err != nil {
-		log.Fatalf("Issue retrieving config for network: "+network+". Please investigate: %v", err)
-	}
-	nodecfg := cfg.Node
+	var nodecfg models.Node
 	var extPeers []models.Node
+	var err error
+	// fill above fields from either client or server
+
 	if nodecfg.IsServer != "yes" { // fill extPeers with client side logic
+		var cfg *config.ClientConfig
+		cfg, err = config.ReadConfig(network)
+		if err != nil {
+			log.Fatalf("Issue retrieving config for network: "+network+". Please investigate: %v", err)
+		}
+		nodecfg = cfg.Node
 		var wcclient nodepb.NodeServiceClient
 
 		conn, err := grpc.Dial(cfg.Server.GRPCAddress,
@@ -334,22 +292,6 @@ func GetExtPeers(macaddress string, network string, server string, dualstack boo
 		if err = json.Unmarshal([]byte(responseObject.Data), &extPeers); err != nil {
 			return nil, err
 		}
-	} else { // fill extPeers with server side logic
-		tempPeers, err := logic.GetExtPeersList(nodecfg.MacAddress, nodecfg.Network)
-		if err != nil {
-			return nil, err
-		}
-		for i := 0; i < len(tempPeers); i++ {
-			extPeers = append(extPeers, models.Node{
-				Address:             tempPeers[i].Address,
-				Address6:            tempPeers[i].Address6,
-				Endpoint:            tempPeers[i].Endpoint,
-				PublicKey:           tempPeers[i].PublicKey,
-				PersistentKeepalive: tempPeers[i].KeepAlive,
-				ListenPort:          tempPeers[i].ListenPort,
-				LocalAddress:        tempPeers[i].LocalAddress,
-			})
-		}
 	}
 	for _, extPeer := range extPeers {
 		pubkey, err := wgtypes.ParseKey(extPeer.PublicKey)

+ 5 - 1
netclient/wireguard/common.go

@@ -18,9 +18,9 @@ import (
 	"github.com/gravitl/netmaker/netclient/server"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
-	//homedir "github.com/mitchellh/go-homedir"
 )
 
+// SetPeers - sets peers on a given WireGuard interface
 func SetPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
 
 	client, err := wgctrl.New()
@@ -95,6 +95,7 @@ func SetPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
 	return nil
 }
 
+// Initializes a WireGuard interface
 func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
 
 	key, err := wgtypes.ParseKey(privkey)
@@ -258,6 +259,7 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 	return err
 }
 
+// SetWGConfig - sets the WireGuard Config of a given network and checks if it needs a peer update
 func SetWGConfig(network string, peerupdate bool) error {
 
 	cfg, err := config.ReadConfig(network)
@@ -291,6 +293,7 @@ func SetWGConfig(network string, peerupdate bool) error {
 	return err
 }
 
+// RemoveConf - removes a configuration for a given WireGuard interface
 func RemoveConf(iface string, printlog bool) error {
 	os := runtime.GOOS
 	var err error
@@ -304,6 +307,7 @@ func RemoveConf(iface string, printlog bool) error {
 	return err
 }
 
+// ApplyConf - applys a conf on disk to WireGuard interface
 func ApplyConf(confPath string) error {
 	os := runtime.GOOS
 	var err error

+ 7 - 2
netclient/wireguard/unix.go

@@ -7,9 +7,9 @@ import (
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
-	//homedir "github.com/mitchellh/go-homedir"
 )
 
+// SetWGKeyConfig - sets the wg conf with a new private key
 func SetWGKeyConfig(network string, serveraddr string) error {
 
 	cfg, err := config.ReadConfig(network)
@@ -48,6 +48,7 @@ func SetWGKeyConfig(network string, serveraddr string) error {
 	return err
 }
 
+// ApplyWGQuickConf - applies wg-quick commands if os supports
 func ApplyWGQuickConf(confPath string) error {
 	if _, err := ncutils.RunCmd("wg-quick up "+confPath, true); err != nil {
 		return err
@@ -55,6 +56,7 @@ func ApplyWGQuickConf(confPath string) error {
 	return nil
 }
 
+// RemoveWGQuickConf - calls wg-quick down
 func RemoveWGQuickConf(confPath string, printlog bool) error {
 	if _, err := ncutils.RunCmd("wg-quick down "+confPath, printlog); err != nil {
 		return err
@@ -62,12 +64,15 @@ func RemoveWGQuickConf(confPath string, printlog bool) error {
 	return nil
 }
 
+// StorePrivKey - stores wg priv key on disk locally
 func StorePrivKey(key string, network string) error {
+	var err error
 	d1 := []byte(key)
-	err := ioutil.WriteFile(ncutils.GetNetclientPathSpecific()+"wgkey-"+network, d1, 0644)
+	err = ioutil.WriteFile(ncutils.GetNetclientPathSpecific()+"wgkey-"+network, d1, 0644)
 	return err
 }
 
+// RetrievePrivKey - reads wg priv key from local disk
 func RetrievePrivKey(network string) (string, error) {
 	dat, err := ioutil.ReadFile(ncutils.GetNetclientPathSpecific() + "wgkey-" + network)
 	return string(dat), err

+ 1 - 0
relay/relay.go

@@ -9,6 +9,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 )
 
+// GetNodeRelay - gets the relay node of a given network
 func GetNodeRelay(network string, relayedNodeAddr string) (models.Node, error) {
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
 	var relay models.Node

+ 33 - 3
scripts/netclient-install.sh

@@ -1,14 +1,44 @@
-#!/bin/sh
+#!/bin/bash
 set -e
 
-if [[ $EUID -ne 0 ]]; then
+if [ "$EUID" -ne 0 ]; then
    echo "This script must be run as root" 
    exit 1
 fi
 
 [ -z "$KEY" ] && KEY=nokey;
+[ -z "$VERSION" ] && echo "no \$VERSION provided, fallback to latest" && VERSION=latest;
 
-wget -O netclient https://github.com/gravitl/netmaker/releases/download/latest/netclient
+dist=netclient
+
+echo "OS Version = $OSTYPE"
+echo "Netclient Version = $VERSION"
+
+if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+	arch=$(uname -i)
+	echo "CPU ARCH = $arch"
+	if [ "$arch" == 'x86_64' ];
+	then 
+		dist=netclient 
+	fi
+	if [ "$arch" == 'x86_32' ];
+	then
+		dist=netclient-32
+	fi
+	if [ "$arch" == 'armv*' ];
+	then
+		dist=netclient-arm64
+	fi
+elif [[ "$OSTYPE" == "darwin"* ]]; then
+        dist=netclient-darwin
+else
+        echo "This OS is not currently supported via automated install" 
+        exit 1
+fi
+
+echo "Binary = $dist"
+
+wget -O netclient https://github.com/gravitl/netmaker/releases/download/$VERSION/netclient
 chmod +x netclient
 sudo ./netclient join -t $KEY
 rm -f netclient

Some files were not shown because too many files changed in this diff