Browse Source

Merge pull request #1093 from gravitl/v0.14.0

V0.14.0
dcarns 3 years ago
parent
commit
c147cc2eb9
63 changed files with 1225 additions and 431 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 78 27
      .github/workflows/buildandrelease.yml
  3. 46 6
      .github/workflows/test.yml
  4. 2 1
      .gitignore
  5. 2 2
      README.md
  6. 5 1
      auth/google.go
  7. 7 8
      compose/docker-compose.contained.yml
  8. 2 1
      compose/docker-compose.hostnetwork.yml
  9. 4 3
      compose/docker-compose.nocaddy.yml
  10. 3 2
      compose/docker-compose.nodns.yml
  11. 4 3
      compose/docker-compose.reference.yml
  12. 4 3
      compose/docker-compose.yml
  13. 0 1
      config/config.go
  14. 10 3
      controllers/network.go
  15. 2 2
      controllers/node_test.go
  16. 0 1
      dev.yaml
  17. 2 3
      docker/Caddyfile
  18. 13 3
      go.mod
  19. 54 9
      go.sum
  20. 45 7
      logic/gateway.go
  21. 50 8
      logic/networks.go
  22. 19 24
      logic/peers.go
  23. 3 0
      logic/util.go
  24. 5 3
      logic/wireguard.go
  25. 19 13
      mq/handlers.go
  26. 12 11
      mq/mq.go
  27. 10 2
      mq/util.go
  28. 0 62
      netclient/auth/auth.go
  29. 3 9
      netclient/command/commands.go
  30. 1 11
      netclient/config/config.go
  31. 31 0
      netclient/config/util.go
  32. 1 1
      netclient/daemon/common.go
  33. 2 2
      netclient/daemon/macos.go
  34. 36 77
      netclient/daemon/windows.go
  35. 19 12
      netclient/functions/common.go
  36. 27 33
      netclient/functions/daemon.go
  37. 3 11
      netclient/functions/join.go
  38. 4 4
      netclient/functions/list.go
  39. 1 5
      netclient/functions/mqpublish.go
  40. 33 0
      netclient/gui/components/buttons.go
  41. 22 0
      netclient/gui/components/colors.go
  42. 23 0
      netclient/gui/components/text.go
  43. 39 0
      netclient/gui/components/toolbar.go
  44. 21 0
      netclient/gui/components/views/confirm.go
  45. 25 0
      netclient/gui/components/views/content.go
  46. 62 0
      netclient/gui/components/views/join.go
  47. 167 0
      netclient/gui/components/views/networks.go
  48. 38 0
      netclient/gui/components/views/notification.go
  49. 44 0
      netclient/gui/components/views/state.go
  50. 111 0
      netclient/gui/gui.go
  51. BIN
      netclient/gui/nm-logo-sm.png
  52. 19 0
      netclient/local/local.go
  53. 10 4
      netclient/main.go
  54. 22 0
      netclient/main_gui.go
  55. 9 4
      netclient/ncutils/netclientutils.go
  56. 1 1
      netclient/netclient.exe.manifest.xml
  57. 3 3
      netclient/versioninfo.json
  58. BIN
      netclient/windowsdata/resource/netclient.ico
  59. BIN
      netclient/windowsdata/resource/netmaker.ico
  60. 26 18
      netclient/wireguard/common.go
  61. BIN
      netmaker.exe
  62. 15 20
      scripts/nm-quick.sh
  63. 5 7
      servercfg/serverconf.go

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

@@ -31,6 +31,7 @@ body:
       label: Version
       description: What version are you running?
       options:
+        - v0.14.0      
         - v0.13.1
         - v0.13.0
         - v0.12.2

+ 78 - 27
.github/workflows/buildandrelease.yml

@@ -38,7 +38,7 @@ jobs:
           fi
           echo "::set-output name=tag::${{ env.NETMAKER_VERSION }}"
           echo "::set-output name=version::${{ env.PACKAGE_VERSION }}"
-  netmaker:        
+  netmaker:
     runs-on: ubuntu-latest
     needs: version
     steps:
@@ -87,10 +87,12 @@ jobs:
         uses: actions/setup-go@v2
         with:
           go-version: 1.18
-      - name: Build
+
+      - name: Build cli
         run: |
           cd netclient
           env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient main.go
+
       - name: Upload netclient x86 to Release
         continue-on-error: true
         uses: svenstaro/upload-release-action@v2
@@ -102,12 +104,29 @@ jobs:
           prerelease: true
           asset_name: netclient
 
+      - name: build gui
+        run: |
+          sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
+          go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-gui .
+
+      - name: Upload netclient x86 gui to Release
+        continue-on-error: true
+        uses: svenstaro/upload-release-action@v2
+        with:
+          repo_token: ${{ secrets.GITHUB_TOKEN }}
+          file: netclient/build/netclient-gui
+          tag: ${{ env.NETMAKER_VERSION }}
+          overwrite: true
+          prerelease: true
+          asset_name: netclient-gui
+
       - name: Package x86 deb
         continue-on-error: true
         uses: gravitl/github-action-fpm@master
         with:
           fpm_args: './netclient/build/netclient=/sbin/netclient ./netclient/build/netclient.service=/lib/systemd/system/netclient.service'
           fpm_opts: '-s dir -t deb --architecture amd64 --version ${{ env.PACKAGE_VERSION }}'
+
       - name: Upload x86 deb to Release
         continue-on-error: true
         uses: svenstaro/upload-release-action@v2
@@ -125,12 +144,13 @@ jobs:
         with:
           fpm_args: './netclient/build/netclient=/sbin/netclient ./netclient/build/netclient.service=/lib/systemd/system/netclient.service'
           fpm_opts: '-s dir -t rpm --architecture amd64 --version ${{ env.PACKAGE_VERSION }}'
+
       - name: Upload x86 rpm to Release
         continue-on-error: true
         uses: svenstaro/upload-release-action@v2
         with:
           repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient-${{ env.PACKAGE_VERSION }}-1.x86_64.rpm 
+          file: netclient-${{ env.PACKAGE_VERSION }}-1.x86_64.rpm
           tag: ${{ env.NETMAKER_VERSION }}
           overwrite: true
           prerelease: true
@@ -143,6 +163,7 @@ jobs:
           # arch has particular path requirements --- cannot write to a symbolic link e.g. /sbin and /lib
           fpm_args: './netclient/build/netclient=/usr/bin/netclient ./netclient/build/netclient.service=/usr/lib/systemd/system/netclient.service'
           fpm_opts: '-s dir -t pacman --architecture amd64 --version ${{ env.PACKAGE_VERSION }}'
+
       - name: Upload x86 pacman to Release
         continue-on-error: true
         uses: svenstaro/upload-release-action@v2
@@ -177,25 +198,27 @@ jobs:
           env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-arm6/netclient main.go
           env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-arm7/netclient main.go
           env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-arm64/netclient main.go
+
       - name: Upload arm5 to Release
         uses: svenstaro/upload-release-action@v2
-        with: 
-          repo_token: ${{ secrets.GITHUB_TOKEN }} 
+        with:
+          repo_token: ${{ secrets.GITHUB_TOKEN }}
           file: netclient/build/netclient-arm5/netclient
           tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true 
+          overwrite: true
           prerelease: true
           asset_name: netclient-arm5
 
       - name: Upload arm6 to Release
         uses: svenstaro/upload-release-action@v2
-        with: 
+        with:
           repo_token: ${{ secrets.GITHUB_TOKEN }}
           file: netclient/build/netclient-arm6/netclient
           tag: ${{ env.NETMAKER_VERSION }}
           overwrite: true
           prerelease: true
           asset_name: netclient-arm6
+
       - name: Upload arm7 to Release
         uses: svenstaro/upload-release-action@v2
         with:
@@ -205,7 +228,7 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient-arm7
-     
+
       - name: Upload arm64 to Release
         continue-on-error: true
         uses: svenstaro/upload-release-action@v2
@@ -216,6 +239,7 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient-arm64
+
       - name: Package arm64 deb
         continue-on-error: true
         uses: gravitl/github-action-fpm@master
@@ -232,12 +256,14 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient_${{ env.PACKAGE_VERSION }}_arm64.deb
+
       - name: Package arm64 rpm
         continue-on-error: true
         uses: gravitl/github-action-fpm@master
         with:
           fpm_args: './netclient/build/netclient-arm64/netclient=/sbin/netclient ./netclient/build/netclient.service=/lib/systemd/netclient.service'
           fpm_opts: '-s dir -t rpm --architecture arm64 --version ${{ env.PACKAGE_VERSION }}'
+
       - name: Upload arm64 rpm to Release
         continue-on-error: true
         uses: svenstaro/upload-release-action@v2
@@ -269,6 +295,7 @@ jobs:
         run: |
           cd netclient
           env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -X 'main.version=$NETMAKER_VERSION'" -o build/netclient-mipsle/netclient main.go && upx build/netclient-mipsle/netclient
+
       - name: Upload mipsle to Release
         uses: svenstaro/upload-release-action@v2
         with:
@@ -278,7 +305,7 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient-mipsle
-   
+
   netclient-freebsd:
     runs-on: ubuntu-latest
     needs: version
@@ -312,8 +339,8 @@ jobs:
           tag: ${{ env.NETMAKER_VERSION }}
           overwrite: true
           prerelease: true
-          asset_name: netclient-freebsd      
-          
+          asset_name: netclient-freebsd
+
       - name: Upload freebsd-arm5 to Release
         uses: svenstaro/upload-release-action@v2
         with:
@@ -323,7 +350,7 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient-freebsd-arm5
-          
+
       - name: Upload freebsd-arm6 to Release
         uses: svenstaro/upload-release-action@v2
         with:
@@ -333,7 +360,7 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient-freebsd-arm6
-          
+
       - name: Upload freebsd-arm7 to Release
         uses: svenstaro/upload-release-action@v2
         with:
@@ -343,7 +370,7 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient-freebsd-arm7
-          
+
       - name: Upload freebsd-arm64 to Release
         uses: svenstaro/upload-release-action@v2
         with:
@@ -355,7 +382,7 @@ jobs:
           asset_name: netclient-freebsd-arm64
 
   netclient-darwin:
-    runs-on: ubuntu-latest
+    runs-on: macos-latest
     needs: version
     steps:
       - name: Checkout
@@ -366,16 +393,16 @@ jobs:
           VERSION=${{needs.version.outputs.version}}
           echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
           echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Setup go                                    
-        uses: actions/setup-go@v2                        
-        with:                                           
+      - name: Setup go
+        uses: actions/setup-go@v2
+        with:
           go-version: 1.18
-      - name: Build                                   
-        run: |                                       
-          cd netclient                              
-          env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient main.go
-          env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-arm64/netclient main.go
-      - name: Upload darwin-arm64 to Release
+      - name: Build
+        run: |
+          cd netclient
+          env GOOS=darwin GOARCH=amd64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient .
+          env CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-arm64/netclient main.go
+      - name: Upload darwin-amd64 to Release
         uses: svenstaro/upload-release-action@v2
         with:
           repo_token: ${{ secrets.GITHUB_TOKEN }}
@@ -396,7 +423,7 @@ jobs:
           asset_name: netclient-darwin-arm64
 
   netclient-windows:
-    runs-on: ubuntu-latest
+    runs-on: windows-latest
     needs: version
     steps:
       - name: Checkout
@@ -407,14 +434,26 @@ jobs:
           VERSION=${{needs.version.outputs.version}}
           echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
           echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
+        shell: bash
       - name: Setup go
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: 1.18
+      - name: Mysys2 setup
+        uses: msys2/setup-msys2@v2
+        with:
+          install: >-
+            git
+            mingw-w64-x86_64-toolchain
       - name: Build
         run: |
+          echo $(go env GOPATH)/bin >> $GITHUB_PATH
           cd netclient
-          env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient.exe main.go
+          go get -v github.com/josephspurrier/goversioninfo
+          go install -v github.com/josephspurrier/goversioninfo/cmd/goversioninfo
+          go generate
+          go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient.exe .
+
       - name: Upload netclient windows to Release
         continue-on-error: true
         uses: svenstaro/upload-release-action@v2
@@ -425,3 +464,15 @@ jobs:
           overwrite: true
           prerelease: true
           asset_name: netclient.exe
+
+  linux-packages:
+    runs-on: ubuntu-latest
+    needs: [version, netclient-x86, netclient-arm]
+    steps:
+      - name: Repository Dispatch
+        uses: peter-evans/[email protected]
+        with:
+          token: ${{ secrets.PERS_TOKEN_FOR_NETMAKER_DEVOPS}}
+          repository: gravitl/netmaker-devops
+          event-type: build-packages
+          client-payload: '{"VERSION": "${{ env.PACKAGE_VERSION }}"}'

+ 46 - 6
.github/workflows/test.yml

@@ -12,7 +12,7 @@ jobs:
       - name: Setup Go
         uses: actions/setup-go@v2
         with:
-            go-version: 1.18
+          go-version: 1.18
       - name: Build
         run: |
          env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build main.go
@@ -21,13 +21,53 @@ jobs:
          env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build main.go
          env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
          env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
+  linux-gui:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Setup Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.18
+      - name: Build
+        run: |
+         sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
+         env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags=gui main.go
+  mac-gui:
+    runs-on: macos-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Setup Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.18
+      - name: Build mac
+        run: |
+          env CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -tags=gui main.go
+  win-gui:
+    runs-on: windows-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Setup Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.18
+      - name: Mysys2 setup
+        uses: msys2/setup-msys2@v2
+        with:
+          install: >-
+            git
+            mingw-w64-x86_64-toolchain
+      - name: Build win gui
+        run: |
+          env CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -tags=gui main.go
   tests:
     env:
       DATABASE: sqlite
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os: [macos-latest, ubuntu-latest, windows-latest]
+    runs-on: ubuntu-latest
     steps:
       - name: Checkout
         uses: actions/checkout@v2
@@ -37,8 +77,8 @@ jobs:
           go-version: 1.18
       - name: run tests
         run: |
+            sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
             go test -p 1 ./... -v
         env:
           DATABASE: sqlite
           CLIENT_MODE: "off"
-

+ 2 - 1
.gitignore

@@ -4,6 +4,7 @@ netmaker-arm64
 netmaker-32
 netmaker-amd64
 netclient/netclient
+netclient/netclient.syso
 netclient/build
 netclient/build/
 !netclient/build/netclient.service
@@ -20,4 +21,4 @@ controllers/data/
 data/
 .vscode/
 .idea/
-
+netmaker.exe

+ 2 - 2
README.md

@@ -10,7 +10,7 @@ a platform for modern, blazing fast virtual networks
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.13.1-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.14.0-informational?style=flat-square" />
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker" />
@@ -43,7 +43,7 @@ a platform for modern, blazing fast virtual networks
 **For production-grade installations, visit the [Install Docs](https://netmaker.readthedocs.io/en/master/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, 80, 53, and 51821-51830/udp on the VM firewall and in cloud security settings.
+2. Open ports 443, 80, 53, 8883, and 51821-51830/udp on the VM firewall and in cloud security settings.
 3. Run the script **(see below for optional configurations)**:
 
 `wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh | sudo bash`

+ 5 - 1
auth/google.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"time"
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
@@ -99,7 +100,10 @@ func getGoogleUserInfo(state string, code string) (*googleOauthUser, error) {
 	if err != nil {
 		return nil, fmt.Errorf("failed to convert token to json: %s", err.Error())
 	}
-	response, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token.AccessToken)
+	client := &http.Client{
+		Timeout: time.Second * 30,
+	}
+	response, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token.AccessToken)
 	if err != nil {
 		return nil, fmt.Errorf("failed getting user info: %s", err.Error())
 	}

+ 7 - 8
compose/docker-compose.contained.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.13.1
+    image: gravitl/netmaker:v0.14.0
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
@@ -35,19 +35,17 @@ services:
       MQ_HOST: "mq"
       HOST_NETWORK: "off"
       VERBOSITY: "1"
-      MANAGE_IPTABLES: "off"
+      MANAGE_IPTABLES: "on"
+      PORT_FORWARD_SERVICES: "dns"
     ports:
       - "51821-51830:51821-51830/udp"
-      - "8081:8081"
   netmaker-ui:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.13.1
+    image: gravitl/netmaker-ui:v0.14.0
     links:
       - "netmaker:api"
-    ports:
-      - "8082:80"
     environment:
       BACKEND_URL: "https://api.NETMAKER_BASE_DOMAIN"
     restart: always
@@ -64,7 +62,9 @@ services:
     image: caddy:latest
     container_name: caddy
     restart: unless-stopped
-    network_mode: host # Wants ports 80 and 443!
+    ports:
+      - "80:80"
+      - "443:443"
     volumes:
       - /root/Caddyfile:/etc/caddy/Caddyfile
       # - $PWD/site:/srv # you could also serve a static site in site folder
@@ -77,7 +77,6 @@ services:
     container_name: mq
     restart: unless-stopped
     ports:
-      - "127.0.0.1:1883:1883"
       - "8883:8883"
     volumes:
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf

+ 2 - 1
compose/docker-compose.hostnetwork.yml

@@ -32,7 +32,8 @@ services:
       HOST_NETWORK: "on"
       NODE_ID: "netmaker-server-1"
       VERBOSITY: "1"
-      MANAGE_IPTABLES: "off"
+      MANAGE_IPTABLES: "on"
+      PORT_FORWARD_SERVICES: "dns"
   netmaker-ui:
     container_name: netmaker-ui
     depends_on:

+ 4 - 3
compose/docker-compose.nocaddy.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.13.1
+    image: gravitl/netmaker:v0.14.0
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
@@ -35,7 +35,8 @@ services:
       MQ_HOST: "mq"
       HOST_NETWORK: "off"
       VERBOSITY: "1"
-      MANAGE_IPTABLES: "off"
+      MANAGE_IPTABLES: "on"
+      PORT_FORWARD_SERVICES: "dns"
     ports:
       - "51821-51830:51821-51830/udp"
       - "8081:8081"
@@ -43,7 +44,7 @@ services:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.13.1
+    image: gravitl/netmaker-ui:v0.14.0
     links:
       - "netmaker:api"
     ports:

+ 3 - 2
compose/docker-compose.nodns.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.13.1
+    image: gravitl/netmaker:v0.14.0
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
@@ -35,7 +35,8 @@ services:
       MQ_HOST: "mq"
       HOST_NETWORK: "off"
       VERBOSITY: "1"
-      MANAGE_IPTABLES: "off"
+      MANAGE_IPTABLES: "on"
+      PORT_FORWARD_SERVICES: "dns"
     ports:
       - "51821-51830:51821-51830/udp"
       - "8081:8081"

+ 4 - 3
compose/docker-compose.reference.yml

@@ -2,7 +2,7 @@ services:
   netmaker: # The Primary Server for running Netmaker
     privileged: true # Necessary to run sudo/root level commands on host system. Likely using this if running with host networking on.
     container_name: netmaker
-    image: gravitl/netmaker:v0.13.1
+    image: gravitl/netmaker:v0.14.0
     volumes: # Volume mounts necessary for CLIENT_MODE to control wireguard networking on host (except dnsconfig, which is where dns config files are stored for use by CoreDNS)
       - dnsconfig:/root/config/dnsconfig # Netmaker writes Corefile to this location, which gets mounted by CoreDNS for DNS configuration.
       - sqldata:/root/data
@@ -37,7 +37,8 @@ services:
       TELEMETRY: "on" # Whether or not to send telemetry data to help improve Netmaker. Switch to "off" to opt out of sending telemetry.
       MQ_HOST: "mq" # the address of the mq server. If running from docker compose it will be "mq". Otherwise, need to input address. If using "host networking", it will find and detect the IP of the mq container.
       HOST_NETWORK: "off" # whether or not host networking is turned on. Only turn on if configured for host networking (see docker-compose.hostnetwork.yml). Will set host-level settings like iptables.
-      MANAGE_IPTABLES: "off" # deprecated
+      MANAGE_IPTABLES: "on" # deprecated
+      PORT_FORWARD_SERVICES: "dns" # decide which services to port forward ("dns","ssh", or "mq")
     ports:
       - "51821-51830:51821-51830/udp"
       - "8081:8081"
@@ -45,7 +46,7 @@ services:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.13.1
+    image: gravitl/netmaker-ui:v0.14.0
     links:
       - "netmaker:api"
     ports:

+ 4 - 3
compose/docker-compose.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.13.1
+    image: gravitl/netmaker:v0.14.0
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
@@ -35,7 +35,8 @@ services:
       MQ_HOST: "mq"
       HOST_NETWORK: "off"
       VERBOSITY: "1"
-      MANAGE_IPTABLES: "off"
+      PORT_FORWARD_SERVICES: "dns"
+      MANAGE_IPTABLES: "on"
     ports:
       - "51821-51830:51821-51830/udp"
       - "8081:8081"
@@ -43,7 +44,7 @@ services:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.13.1
+    image: gravitl/netmaker-ui:v0.14.0
     links:
       - "netmaker:api"
     ports:

+ 0 - 1
config/config.go

@@ -61,7 +61,6 @@ type ServerConfig struct {
 	DisplayKeys           string `yaml:"displaykeys"`
 	AzureTenant           string `yaml:"azuretenant"`
 	RCE                   string `yaml:"rce"`
-	Debug                 bool   `yaml:"debug"`
 	Telemetry             string `yaml:"telemetry"`
 	ManageIPTables        string `yaml:"manageiptables"`
 	PortForwardServices   string `yaml:"portforwardservices"`

+ 10 - 3
controllers/network.go

@@ -147,19 +147,26 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
 		newNetwork.DefaultPostUp = network.DefaultPostUp
 	}
 
-	rangeupdate, localrangeupdate, holepunchupdate, err := logic.UpdateNetwork(&network, &newNetwork)
+	rangeupdate4, rangeupdate6, localrangeupdate, holepunchupdate, err := logic.UpdateNetwork(&network, &newNetwork)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
 	}
 
-	if rangeupdate {
+	if rangeupdate4 {
 		err = logic.UpdateNetworkNodeAddresses(network.NetID)
 		if err != nil {
 			returnErrorResponse(w, r, formatError(err, "internal"))
 			return
 		}
 	}
+	if rangeupdate6 {
+		err = logic.UpdateNetworkNodeAddresses6(network.NetID)
+		if err != nil {
+			returnErrorResponse(w, r, formatError(err, "internal"))
+			return
+		}
+	}
 	if localrangeupdate {
 		err = logic.UpdateNetworkLocalAddresses(network.NetID)
 		if err != nil {
@@ -174,7 +181,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	if rangeupdate || localrangeupdate || holepunchupdate {
+	if rangeupdate4 || rangeupdate6 || localrangeupdate || holepunchupdate {
 		nodes, err := logic.GetNetworkNodes(network.NetID)
 		if err != nil {
 			returnErrorResponse(w, r, formatError(err, "internal"))

+ 2 - 2
controllers/node_test.go

@@ -25,13 +25,13 @@ func TestCreateEgressGateway(t *testing.T) {
 		assert.EqualError(t, err, "could not find any records")
 	})
 	t.Run("Non-linux node", func(t *testing.T) {
-		createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: "testnode", Endpoint: "10.0.0.1", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet", OS: "freebsd"}
+		createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: "testnode", Endpoint: "10.0.0.1", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet", OS: "windows"}
 		err := logic.CreateNode(&createnode)
 		assert.Nil(t, err)
 		gateway.NodeID = createnode.ID
 		node, err := logic.CreateEgressGateway(gateway)
 		assert.Equal(t, models.Node{}, node)
-		assert.EqualError(t, err, "freebsd is unsupported for egress gateways")
+		assert.EqualError(t, err, "windows is unsupported for egress gateways")
 	})
 	t.Run("Success", func(t *testing.T) {
 		deleteAllNodes()

+ 0 - 1
dev.yaml

@@ -33,7 +33,6 @@ server:
   displaykeys: ""
   azuretenant: ""
   rce: "off"
-  debug: ""
   telemetry: ""
   manageiptables: "off"
   portforwardservices: ""

+ 2 - 3
docker/Caddyfile

@@ -21,11 +21,10 @@ https://dashboard.NETMAKER_BASE_DOMAIN {
                 -Server
         }
 
-        reverse_proxy http://127.0.0.1:8082
+        reverse_proxy http://netmaker-ui
 }
 
 # API
 https://api.NETMAKER_BASE_DOMAIN {
-        reverse_proxy http://127.0.0.1:8081
+        reverse_proxy http://netmaker:8081
 }
-

+ 13 - 3
go.mod

@@ -4,7 +4,7 @@ go 1.18
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.3.5
-	github.com/go-playground/validator/v10 v10.10.1
+	github.com/go-playground/validator/v10 v10.11.0
 	github.com/golang-jwt/jwt/v4 v4.4.1
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/uuid v1.3.0
@@ -16,7 +16,7 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.7.1
 	github.com/txn2/txeh v1.3.0
-	github.com/urfave/cli/v2 v2.4.8
+	github.com/urfave/cli/v2 v2.6.0
 	golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
 	golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
@@ -31,7 +31,7 @@ require (
 
 require (
 	filippo.io/edwards25519 v1.0.0-rc.1
-	github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
+	fyne.io/fyne/v2 v2.1.4
 	github.com/guumaster/hostctl v1.1.2
 	github.com/kr/pretty v0.3.0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
@@ -48,9 +48,15 @@ require (
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/felixge/httpsnoop v1.0.1 // indirect
+	github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
+	github.com/fsnotify/fsnotify v1.4.9 // indirect
+	github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
+	github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
 	github.com/go-playground/locales v0.14.0 // indirect
 	github.com/go-playground/universal-translator v0.18.0 // indirect
+	github.com/godbus/dbus/v5 v5.0.4 // indirect
 	github.com/gogo/protobuf v1.3.1 // indirect
+	github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
 	github.com/google/go-cmp v0.5.7 // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/josharian/native v1.0.0 // indirect
@@ -67,7 +73,11 @@ require (
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/seancfoley/bintree v1.0.1 // indirect
 	github.com/spf13/afero v1.3.2 // indirect
+	github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
+	github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
+	github.com/yuin/goldmark v1.3.8 // indirect
+	golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	google.golang.org/appengine v1.4.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect

+ 54 - 9
go.sum

@@ -3,10 +3,15 @@ cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
 filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
+fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
+fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
 github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -43,26 +48,37 @@ github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqn
 github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
 github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
+github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU=
+github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4=
-github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
 github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
 github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
 github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig=
-github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
+github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
+github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
+github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -79,7 +95,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -100,7 +115,9 @@ github.com/guumaster/tablewriter v0.0.9 h1:qyswXhSCI1SWYH78MLApi8AfL8JsWZWAUkZLO
 github.com/guumaster/tablewriter v0.0.9/go.mod h1:9B1xy1BLPtcVAeYjC1EXPxcklqnzk7dU2c3ywGbUnKY=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -122,6 +139,7 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
 github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
@@ -141,6 +159,8 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCL
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@@ -198,6 +218,10 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
+github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
+github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
+github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
+github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -213,12 +237,16 @@ github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli/v2 v2.4.8 h1:9HuvvddU3oEJr1tJlwUVVsk3snVWMuKSpyAO+SzTNuI=
-github.com/urfave/cli/v2 v2.4.8/go.mod h1:oDzoM7pVwz6wHn5ogWgFUU1s4VJayeQS+aEZDqXIEJs=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/urfave/cli/v2 v2.6.0 h1:yj2Drkflh8X/zUrkWlWlUjZYHyWN7WMmpVxyxXIUyv8=
+github.com/urfave/cli/v2 v2.6.0/go.mod h1:oDzoM7pVwz6wHn5ogWgFUU1s4VJayeQS+aEZDqXIEJs=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
+github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@@ -227,14 +255,18 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -243,10 +275,11 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -259,6 +292,7 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr
 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=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -269,13 +303,17 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -298,6 +336,10 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -321,6 +363,7 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -331,6 +374,8 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 45 - 7
logic/gateway.go

@@ -17,7 +17,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	if err != nil {
 		return models.Node{}, err
 	}
-	if node.OS != "linux" { // add in darwin later
+	if node.OS != "linux" && node.OS != "freebsd" { // add in darwin later
 		return models.Node{}, errors.New(node.OS + " is unsupported for egress gateways")
 	}
 	err = ValidateEgressGateway(gateway)
@@ -26,8 +26,30 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	}
 	node.IsEgressGateway = "yes"
 	node.EgressGatewayRanges = gateway.Ranges
-	postUpCmd := "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT; iptables -A FORWARD -o " + node.Interface + " -j ACCEPT; iptables -t nat -A POSTROUTING -o " + gateway.Interface + " -j MASQUERADE"
-	postDownCmd := "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT; iptables -D FORWARD -o " + node.Interface + " -j ACCEPT; iptables -t nat -D POSTROUTING -o " + gateway.Interface + " -j MASQUERADE"
+	postUpCmd := ""
+	postDownCmd := ""
+	if node.OS == "linux" {
+		postUpCmd = "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT ; "
+		postUpCmd += "iptables -A FORWARD -o " + node.Interface + " -j ACCEPT ; "
+		postUpCmd += "iptables -t nat -A POSTROUTING -o " + gateway.Interface + " -j MASQUERADE"
+		postDownCmd = "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT ; "
+		postDownCmd += "iptables -D FORWARD -o " + node.Interface + " -j ACCEPT ; "
+		postDownCmd += "iptables -t nat -D POSTROUTING -o " + gateway.Interface + " -j MASQUERADE"
+	}
+	if node.OS == "freebsd" {
+		postUpCmd = "kldload ipfw ipfw_nat ; "
+		postUpCmd += "ipfw disable one_pass ; "
+		postUpCmd += "ipfw nat 1 config if " + gateway.Interface + " same_ports unreg_only reset ; "
+		postUpCmd += "ipfw add 64000 reass all from any to any in ; "
+		postUpCmd += "ipfw add 64000 nat 1 ip from any to any in via " + gateway.Interface + " ; "
+		postUpCmd += "ipfw add 64000 check-state ; "
+		postUpCmd += "ipfw add 64000 nat 1 ip from any to any out via " + gateway.Interface + " ; "
+		postUpCmd += "ipfw add 65534 allow ip from any to any ; "
+		postDownCmd = "ipfw delete 64000 ; "
+		postDownCmd += "ipfw delete 65534 ; "
+		postDownCmd += "kldunload ipfw_nat ipfw"
+
+	}
 	if gateway.PostUp != "" {
 		postUpCmd = gateway.PostUp
 	}
@@ -89,8 +111,20 @@ func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
 	node.PostUp = ""
 	node.PostDown = ""
 	if node.IsIngressGateway == "yes" { // check if node is still an ingress gateway before completely deleting postdown/up rules
-		node.PostUp = "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT; iptables -A FORWARD -o " + node.Interface + " -j ACCEPT; iptables -t nat -A POSTROUTING -o " + node.Interface + " -j MASQUERADE"
-		node.PostDown = "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT; iptables -D FORWARD -o " + node.Interface + " -j ACCEPT; iptables -t nat -D POSTROUTING -o " + node.Interface + " -j MASQUERADE"
+		if node.OS == "linux" {
+			node.PostUp = "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT ; "
+			node.PostUp += "iptables -A FORWARD -o " + node.Interface + " -j ACCEPT ; "
+			node.PostUp += "iptables -t nat -A POSTROUTING -o " + node.Interface + " -j MASQUERADE"
+			node.PostDown = "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT ; "
+			node.PostDown += "iptables -D FORWARD -o " + node.Interface + " -j ACCEPT ; "
+			node.PostDown += "iptables -t nat -D POSTROUTING -o " + node.Interface + " -j MASQUERADE"
+		}
+		if node.OS == "freebsd" {
+			node.PostUp = ""
+			node.PostDown = "ipfw delete 64000 ; "
+			node.PostDown += "ipfw delete 65534 ; "
+			node.PostDown += "kldunload ipfw_nat ipfw"
+		}
 	}
 	node.SetLastModified()
 
@@ -125,8 +159,12 @@ func CreateIngressGateway(netid string, nodeid string) (models.Node, error) {
 	}
 	node.IsIngressGateway = "yes"
 	node.IngressGatewayRange = network.AddressRange
-	postUpCmd := "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT; iptables -A FORWARD -o " + node.Interface + " -j ACCEPT; iptables -t nat -A POSTROUTING -o " + node.Interface + " -j MASQUERADE"
-	postDownCmd := "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT; iptables -D FORWARD -o " + node.Interface + " -j ACCEPT; iptables -t nat -D POSTROUTING -o " + node.Interface + " -j MASQUERADE"
+	postUpCmd := "iptables -A FORWARD -i " + node.Interface + " -j ACCEPT ; "
+	postUpCmd += "iptables -A FORWARD -o " + node.Interface + " -j ACCEPT ; "
+	postUpCmd += "iptables -t nat -A POSTROUTING -o " + node.Interface + " -j MASQUERADE"
+	postDownCmd := "iptables -D FORWARD -i " + node.Interface + " -j ACCEPT ; "
+	postDownCmd += "iptables -D FORWARD -o " + node.Interface + " -j ACCEPT ; "
+	postDownCmd += "iptables -t nat -D POSTROUTING -o " + node.Interface + " -j MASQUERADE"
 	if node.PostUp != "" {
 		if !strings.Contains(node.PostUp, postUpCmd) {
 			postUpCmd = node.PostUp + "; " + postUpCmd

+ 50 - 8
logic/networks.go

@@ -447,7 +447,7 @@ func UpdateNetworkNodeAddresses(networkName string) error {
 		var node models.Node
 		err := json.Unmarshal([]byte(value), &node)
 		if err != nil {
-			fmt.Println("error in node address assignment!")
+			logger.Log(1, "error in node ipv4 address assignment!")
 			return err
 		}
 		if node.Network == networkName {
@@ -459,7 +459,7 @@ func UpdateNetworkNodeAddresses(networkName string) error {
 				ipaddr, iperr = UniqueAddress(networkName, false)
 			}
 			if iperr != nil {
-				fmt.Println("error in node  address assignment!")
+				logger.Log(1, "error in node ipv4 address assignment!")
 				return iperr
 			}
 
@@ -475,6 +475,47 @@ func UpdateNetworkNodeAddresses(networkName string) error {
 	return nil
 }
 
+// UpdateNetworkNodeAddresses6 - updates network node addresses
+func UpdateNetworkNodeAddresses6(networkName string) error {
+
+	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
+	if err != nil {
+		return err
+	}
+
+	for _, value := range collections {
+
+		var node models.Node
+		err := json.Unmarshal([]byte(value), &node)
+		if err != nil {
+			logger.Log(1, "error in node ipv6 address assignment!")
+			return err
+		}
+		if node.Network == networkName {
+			var ipaddr string
+			var iperr error
+			if node.IsServer == "yes" {
+				ipaddr, iperr = UniqueAddress6(networkName, true)
+			} else {
+				ipaddr, iperr = UniqueAddress6(networkName, false)
+			}
+			if iperr != nil {
+				logger.Log(1, "error in node ipv6 address assignment!")
+				return iperr
+			}
+
+			node.Address6 = ipaddr
+			data, err := json.Marshal(&node)
+			if err != nil {
+				return err
+			}
+			database.Insert(node.ID, string(data), database.NODES_TABLE_NAME)
+		}
+	}
+
+	return nil
+}
+
 // IsNetworkNameUnique - checks to see if any other networks have the same name (id)
 func IsNetworkNameUnique(network *models.Network) (bool, error) {
 
@@ -497,24 +538,25 @@ func IsNetworkNameUnique(network *models.Network) (bool, error) {
 }
 
 // UpdateNetwork - updates a network with another network's fields
-func UpdateNetwork(currentNetwork *models.Network, newNetwork *models.Network) (bool, bool, bool, error) {
+func UpdateNetwork(currentNetwork *models.Network, newNetwork *models.Network) (bool, bool, bool, bool, error) {
 	if err := ValidateNetwork(newNetwork, true); err != nil {
-		return false, false, false, err
+		return false, false, false, false, err
 	}
 	if newNetwork.NetID == currentNetwork.NetID {
-		hasrangeupdate := newNetwork.AddressRange != currentNetwork.AddressRange
+		hasrangeupdate4 := newNetwork.AddressRange != currentNetwork.AddressRange
+		hasrangeupdate6 := newNetwork.AddressRange6 != currentNetwork.AddressRange6
 		localrangeupdate := newNetwork.LocalRange != currentNetwork.LocalRange
 		hasholepunchupdate := newNetwork.DefaultUDPHolePunch != currentNetwork.DefaultUDPHolePunch
 		data, err := json.Marshal(newNetwork)
 		if err != nil {
-			return false, false, false, err
+			return false, false, false, false, err
 		}
 		newNetwork.SetNetworkLastModified()
 		err = database.Insert(newNetwork.NetID, string(data), database.NETWORKS_TABLE_NAME)
-		return hasrangeupdate, localrangeupdate, hasholepunchupdate, err
+		return hasrangeupdate4, hasrangeupdate6, localrangeupdate, hasholepunchupdate, err
 	}
 	// copy values
-	return false, false, false, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.")
+	return false, false, false, false, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.")
 }
 
 // GetNetwork - gets a network from database

+ 19 - 24
logic/peers.go

@@ -178,25 +178,15 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) {
 	if err != nil {
 		return models.PeerUpdate{}, err
 	}
-	// begin translating netclient logic
-	/*
 
-
-		Go through netclient code and put below
-
-
-
-	*/
 	// #1 Set Keepalive values: set_keepalive
 	// #2 Set local address: set_local - could be a LOT BETTER and fix some bugs with additional logic
 	// #3 Set allowedips: set_allowedips
-	var dns string
 	for _, peer := range currentPeers {
 		if peer.ID == node.ID {
 			//skip yourself
 			continue
 		}
-		dns = dns + fmt.Sprintf("%s %s.%s\n", peer.Address, peer.Name, peer.Network)
 		pubkey, err := wgtypes.ParseKey(peer.PublicKey)
 		if err != nil {
 			return models.PeerUpdate{}, err
@@ -245,23 +235,11 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) {
 			log.Println("ERROR RETRIEVING EXTERNAL PEERS", err)
 		}
 	}
+
 	peerUpdate.Network = node.Network
 	peerUpdate.Peers = peers
 	peerUpdate.ServerAddrs = serverNodeAddresses
-	/*
-
-
-		End translation of netclient code
-
-
-	*/
-	if customDNSEntries, err := GetCustomDNS(peerUpdate.Network); err == nil {
-		for _, entry := range customDNSEntries {
-			// TODO - filter entries based on ACLs / given peers vs nodes in network
-			dns = dns + fmt.Sprintf("%s %s.%s\n", entry.Address, entry.Name, entry.Network)
-		}
-	}
-	peerUpdate.DNS = dns
+	peerUpdate.DNS = getPeerDNS(node.Network)
 	return peerUpdate, nil
 }
 
@@ -387,3 +365,20 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet {
 	}
 	return allowedips
 }
+
+func getPeerDNS(network string) string {
+	var dns string
+	if nodes, err := GetNetworkNodes(network); err == nil {
+		for i := range nodes {
+			dns = dns + fmt.Sprintf("%s %s.%s\n", nodes[i].Address, nodes[i].Name, nodes[i].Network)
+		}
+	}
+
+	if customDNSEntries, err := GetCustomDNS(network); err == nil {
+		for _, entry := range customDNSEntries {
+			// TODO - filter entries based on ACLs / given peers vs nodes in network
+			dns = dns + fmt.Sprintf("%s %s.%s\n", entry.Address, entry.Name, entry.Network)
+		}
+	}
+	return dns
+}

+ 3 - 0
logic/util.go

@@ -202,6 +202,9 @@ func StringSliceContains(slice []string, item string) bool {
 // sets the network server peers of a given node
 func setNetworkServerPeers(serverNode *models.Node) {
 	if currentPeersList, err := getSystemPeers(serverNode); err == nil {
+		if currentPeersList == nil {
+			currentPeersList = make(map[string]string)
+		}
 		if database.SetPeers(currentPeersList, serverNode.Network) {
 			logger.Log(1, "set new peers on network", serverNode.Network)
 		}

+ 5 - 3
logic/wireguard.go

@@ -114,9 +114,11 @@ func getSystemPeers(node *models.Node) (map[string]string, error) {
 	if err != nil {
 		return nil, err
 	}
-	for _, peer := range device.Peers {
-		if IsBase64(peer.PublicKey.String()) && peer.Endpoint != nil && CheckEndpoint(peer.Endpoint.String()) {
-			peers[peer.PublicKey.String()] = peer.Endpoint.String()
+	if device.Peers != nil && len(device.Peers) > 0 {
+		for _, peer := range device.Peers {
+			if IsBase64(peer.PublicKey.String()) && peer.Endpoint != nil && CheckEndpoint(peer.Endpoint.String()) {
+				peers[peer.PublicKey.String()] = peer.Endpoint.String()
+			}
 		}
 	}
 	return peers, nil

+ 19 - 13
mq/handlers.go

@@ -11,7 +11,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
-// DefaultHandler default message queue handler - only called when GetDebug == true
+// DefaultHandler default message queue handler  -- NOT USED
 func DefaultHandler(client mqtt.Client, msg mqtt.Message) {
 	logger.Log(0, "MQTT Message: Topic: ", string(msg.Topic()), " Message: ", string(msg.Payload()))
 }
@@ -81,6 +81,7 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 			logger.Log(1, "error saving node", err.Error())
 			return
 		}
+		updateNodePeers(&currentNode)
 		logger.Log(1, "updated node", id, newNode.Name)
 	}()
 }
@@ -114,20 +115,25 @@ func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
 				return
 			}
 		case ncutils.DONE:
-			currentServerNode, err := logic.GetNetworkServerLocal(currentNode.Network)
-			if err != nil {
-				return
-			}
-			if err := logic.ServerUpdate(&currentServerNode, false); err != nil {
-				logger.Log(1, "server node:", currentServerNode.ID, "failed update")
-				return
-			}
-			if err := PublishPeerUpdate(&currentNode); err != nil {
-				logger.Log(1, "error publishing peer update ", err.Error())
-				return
-			}
+			updateNodePeers(&currentNode)
 		}
 
 		logger.Log(1, "sent peer updates after signal received from", id, currentNode.Name)
 	}()
 }
+
+func updateNodePeers(currentNode *models.Node) {
+	currentServerNode, err := logic.GetNetworkServerLocal(currentNode.Network)
+	if err != nil {
+		logger.Log(1, "failed to get server node failed update\n", err.Error())
+		return
+	}
+	if err := logic.ServerUpdate(&currentServerNode, false); err != nil {
+		logger.Log(1, "server node:", currentServerNode.ID, "failed update")
+		return
+	}
+	if err := PublishPeerUpdate(currentNode); err != nil {
+		logger.Log(1, "error publishing peer update ", err.Error())
+		return
+	}
+}

+ 12 - 11
mq/mq.go

@@ -16,6 +16,9 @@ const KEEPALIVE_TIMEOUT = 60 //timeout in seconds
 // MQ_DISCONNECT - disconnects MQ
 const MQ_DISCONNECT = 250
 
+// MQ_TIMEOUT - timeout for MQ
+const MQ_TIMEOUT = 30
+
 var peer_force_send = 0
 
 // SetupMQTT creates a connection to broker and return client
@@ -31,21 +34,15 @@ func SetupMQTT(publish bool) mqtt.Client {
 	opts.SetWriteTimeout(time.Minute)
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
 		if !publish {
-			if servercfg.GetDebug() {
-				if token := client.Subscribe("#", 2, mqtt.MessageHandler(DefaultHandler)); token.Wait() && token.Error() != nil {
-					client.Disconnect(240)
-					logger.Log(0, "default subscription failed")
-				}
-			}
-			if token := client.Subscribe("ping/#", 2, mqtt.MessageHandler(Ping)); token.Wait() && token.Error() != nil {
+			if token := client.Subscribe("ping/#", 2, mqtt.MessageHandler(Ping)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
 				client.Disconnect(240)
 				logger.Log(0, "ping subscription failed")
 			}
-			if token := client.Subscribe("update/#", 0, mqtt.MessageHandler(UpdateNode)); token.Wait() && token.Error() != nil {
+			if token := client.Subscribe("update/#", 0, mqtt.MessageHandler(UpdateNode)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
 				client.Disconnect(240)
 				logger.Log(0, "node update subscription failed")
 			}
-			if token := client.Subscribe("signal/#", 0, mqtt.MessageHandler(ClientPeerUpdate)); token.Wait() && token.Error() != nil {
+			if token := client.Subscribe("signal/#", 0, mqtt.MessageHandler(ClientPeerUpdate)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
 				client.Disconnect(240)
 				logger.Log(0, "node client subscription failed")
 			}
@@ -57,10 +54,14 @@ func SetupMQTT(publish bool) mqtt.Client {
 	client := mqtt.NewClient(opts)
 	tperiod := time.Now().Add(10 * time.Second)
 	for {
-		if token := client.Connect(); token.Wait() && token.Error() != nil {
+		if token := client.Connect(); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
 			logger.Log(2, "unable to connect to broker, retrying ...")
 			if time.Now().After(tperiod) {
-				log.Fatal(0, "could not connect to broker, exiting ...", token.Error())
+				if token.Error() == nil {
+					log.Fatal(0, "could not connect to broker, token timeout, exiting ...")
+				} else {
+					log.Fatal(0, "could not connect to broker, exiting ...", token.Error())
+				}
 			}
 		} else {
 			break

+ 10 - 2
mq/util.go

@@ -1,8 +1,10 @@
 package mq
 
 import (
+	"errors"
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
@@ -65,8 +67,14 @@ func publish(node *models.Node, dest string, msg []byte) error {
 	if encryptErr != nil {
 		return encryptErr
 	}
-	if token := client.Publish(dest, 0, true, encrypted); token.Wait() && token.Error() != nil {
-		return token.Error()
+	if token := client.Publish(dest, 0, true, encrypted); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
+		var err error
+		if token.Error() == nil {
+			err = errors.New("connection timeout")
+		} else {
+			err = token.Error()
+		}
+		return err
 	}
 	return nil
 }

+ 0 - 62
netclient/auth/auth.go

@@ -7,68 +7,6 @@ import (
 	//    "os"
 )
 
-// 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 := os.ReadFile(home + "nettoken-" + network)
-//	if err != nil {
-//		err = AutoLogin(client, network)
-//		if err != nil {
-//			return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong with Auto Login: %v", err))
-//		}
-//		tokentext, err = ncutils.GetFileWithRetry(home+"nettoken-"+network, 1)
-//		if err != nil {
-//			return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong: %v", err))
-//		}
-//	}
-//	token := string(tokentext)
-//
-//	// Anything linked to this variable will transmit request headers.
-//	md := metadata.New(map[string]string{"authorization": token})
-//	ctx := context.Background()
-//	ctx = metadata.NewOutgoingContext(ctx, md)
-//	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)
-//	if err != nil {
-//		return err
-//	}
-//	pass, err := RetrieveSecret(network)
-//	if err != nil {
-//		return err
-//	}
-//	node := models.Node{
-//		Password:   pass,
-//		MacAddress: cfg.Node.MacAddress,
-//		ID:         cfg.Node.ID,
-//		Network:    network,
-//	}
-//	data, err := json.Marshal(&node)
-//	if err != nil {
-//		return nil
-//	}
-//
-//	login := &nodepb.Object{
-//		Data: string(data),
-//		Type: nodepb.NODE_TYPE,
-//	}
-//	// RPC call
-//	res, err := client.Login(context.TODO(), login)
-//	if err != nil {
-//		return err
-//	}
-//	tokenstring := []byte(res.Data)
-//	err = os.WriteFile(home+"nettoken-"+network, tokenstring, 0600)
-//	if err != nil {
-//		return err
-//	}
-//	return err
-//}
-
 // StoreSecret - stores auth secret locally
 func StoreSecret(key string, network string) error {
 	d1 := []byte(key)

+ 3 - 9
netclient/command/commands.go

@@ -18,7 +18,7 @@ func Join(cfg *config.ClientConfig, privateKey string) error {
 	var err error
 	//join network
 	err = functions.JoinNetwork(cfg, privateKey)
-	if err != nil && !cfg.DebugOn {
+	if err != nil {
 		if !strings.Contains(err.Error(), "ALREADY_INSTALLED") {
 			logger.Log(1, "error installing: ", err.Error())
 			err = functions.LeaveNetwork(cfg.Network, true)
@@ -49,13 +49,7 @@ func Join(cfg *config.ClientConfig, privateKey string) error {
 		return err
 	}
 	logger.Log(1, "joined ", cfg.Network)
-	/*
-		if ncutils.IsWindows() {
-			logger.Log("setting up WireGuard app", 0)
-			time.Sleep(time.Second >> 1)
-			functions.Pull(cfg.Network, true)
-		}
-	*/
+
 	return err
 }
 
@@ -125,7 +119,7 @@ func Pull(cfg *config.ClientConfig) error {
 
 // List - runs list command from cli
 func List(cfg config.ClientConfig) error {
-	err := functions.List(cfg.Network)
+	_, err := functions.List(cfg.Network)
 	return err
 }
 

+ 1 - 11
netclient/config/config.go

@@ -6,8 +6,6 @@ import (
 	"crypto/ed25519"
 	"crypto/x509"
 	"crypto/x509/pkix"
-	"encoding/base64"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"log"
@@ -28,7 +26,6 @@ type ClientConfig struct {
 	Network         string         `yaml:"network"`
 	Daemon          string         `yaml:"daemon"`
 	OperatingSystem string         `yaml:"operatingsystem"`
-	DebugOn         bool           `yaml:"debugon"`
 }
 
 // ServerConfig - struct for dealing with the server information for a netclient
@@ -185,14 +182,8 @@ func ReplaceWithBackup(network string) error {
 func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 	var cfg ClientConfig
 	if c.String("token") != "" {
-		tokenbytes, err := base64.StdEncoding.DecodeString(c.String("token"))
+		accesstoken, err := ParseAccessToken(c.String("token"))
 		if err != nil {
-			log.Println("error decoding token")
-			return cfg, "", err
-		}
-		var accesstoken models.AccessToken
-		if err := json.Unmarshal(tokenbytes, &accesstoken); err != nil {
-			log.Println("error converting token json to object", tokenbytes)
 			return cfg, "", err
 		}
 		cfg.Network = accesstoken.ClientConfig.Network
@@ -217,7 +208,6 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 		if c.String("apiserver") != "" {
 			cfg.Server.API = c.String("apiserver")
 		}
-
 	} else {
 		cfg.Server.AccessKey = c.String("key")
 		cfg.Network = c.String("network")

+ 31 - 0
netclient/config/util.go

@@ -0,0 +1,31 @@
+package config
+
+import (
+	"encoding/base64"
+	"encoding/json"
+
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/models"
+)
+
+var (
+	// GuiActive - indicates if gui is active or not
+	GuiActive = false
+	// GuiRun - holds function for main to call
+	GuiRun interface{}
+)
+
+// ParseAccessToken - used to parse the base64 encoded access token
+func ParseAccessToken(token string) (*models.AccessToken, error) {
+	tokenbytes, err := base64.StdEncoding.DecodeString(token)
+	if err != nil {
+		logger.Log(0, "error decoding token", err.Error())
+		return nil, err
+	}
+	var accesstoken models.AccessToken
+	if err := json.Unmarshal(tokenbytes, &accesstoken); err != nil {
+		logger.Log(0, "error decoding token", err.Error())
+		return nil, err
+	}
+	return &accesstoken, nil
+}

+ 1 - 1
netclient/daemon/common.go

@@ -59,7 +59,7 @@ func Stop() error {
 
 	switch os {
 	case "windows":
-		StopWindowsDaemon()
+		RunWinSWCMD("stop")
 	case "darwin":
 		StopLaunchD()
 	case "linux":

+ 2 - 2
netclient/daemon/macos.go

@@ -96,8 +96,8 @@ func MacDaemonString() string {
 			<string>/usr/local/bin/netclient</string>
 			<string>daemon</string>
 		</array>
-	<key>StandardOutPath</key><string>/etc/netclient/com.gravitl.netclient.log</string>
-	<key>StandardErrorPath</key><string>/etc/netclient/com.gravitl.netclient.log</string>
+	<key>StandardOutPath</key><string>/var/log/com.gravitl.netclient.log</string>
+	<key>StandardErrorPath</key><string>/var/log/com.gravitl.netclient.log</string>
 	<key>RunAtLoad</key>
 	<true/>
 	<key>KeepAlive</key>

+ 36 - 77
netclient/daemon/windows.go

@@ -5,6 +5,7 @@ import (
 	"log"
 	"os"
 	"strings"
+	"time"
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
@@ -27,19 +28,19 @@ func SetupWindowsDaemon() error {
 		}
 		logger.Log(0, "finished daemon setup")
 	}
-	// install daemon, will not overwrite
-	ncutils.RunCmd(strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)+`winsw.exe install`, false)
-	// start daemon, will not restart or start another
-	ncutils.RunCmd(strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)+`winsw.exe start`, false)
-	logger.Log(0, strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)+`winsw.exe start`)
+	//get exact formatted commands
+	RunWinSWCMD("install")
+	time.Sleep(1)
+	RunWinSWCMD("start")
+
 	return nil
 }
 
 // RestartWindowsDaemon - restarts windows service
 func RestartWindowsDaemon() {
-	StopWindowsDaemon()
-	// start daemon, will not restart or start another
-	ncutils.RunCmd(strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)+`winsw.exe start`, false)
+	RunWinSWCMD("stop")
+	time.Sleep(1)
+	RunWinSWCMD("start")
 }
 
 // CleanupWindows - cleans up windows files
@@ -47,8 +48,8 @@ func CleanupWindows() {
 	if !ncutils.FileExists(ncutils.GetNetclientPathSpecific() + "winsw.xml") {
 		writeServiceConfig()
 	}
-	StopWindowsDaemon()
-	RemoveWindowsDaemon()
+	RunWinSWCMD("stop")
+	RunWinSWCMD("uninstall")
 	os.RemoveAll(ncutils.GetNetclientPath())
 	log.Println("Netclient on Windows, uninstalled")
 }
@@ -74,73 +75,31 @@ func writeServiceConfig() error {
 	return nil
 }
 
-// == Daemon ==
+// RunWinSWCMD - Run a command with the winsw.exe tool (start, stop, install, uninstall)
+func RunWinSWCMD(command string) {
 
-// StopWindowsDaemon - stops the Windows daemon
-func StopWindowsDaemon() {
-	logger.Log(0, "stopping Windows, Netclient daemon")
-	// stop daemon, will not overwrite
-	ncutils.RunCmd(strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)+`winsw.exe stop`, true)
-}
+	// check if command allowed
+	allowedCommands := map[string]bool{
+		"start":     true,
+		"stop":      true,
+		"install":   true,
+		"uninstall": true,
+	}
+	if !allowedCommands[command] {
+		logger.Log(0, "command "+command+" unsupported by winsw")
+		return
+	}
 
-// 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)
-	logger.Log(0, "uninstalled Windows, Netclient daemon")
+	// format command
+	dirPath := strings.Replace(ncutils.GetNetclientPathSpecific(), `\\`, `\`, -1)
+	winCmd := fmt.Sprintf(`"%swinsw.exe" "%s"`, dirPath, command)
+	logger.Log(0, "running "+command+" of Windows Netclient daemon")
+
+	// run command and log for success/failure
+	out, err := ncutils.RunCmdFormatted(winCmd, true)
+	if err != nil {
+		logger.Log(0, "error with "+command+" of Windows Netclient daemon: "+err.Error()+" : "+out)
+	} else {
+		logger.Log(0, "successfully ran "+command+" of Windows Netclient daemon")
+	}
 }
-
-// func copyWinswOver() error {
-
-// 	input, err := ioutil.ReadFile(".\\winsw.exe")
-// 	if err != nil {
-// 		logger.Log(0, "failed to find winsw.exe")
-// 		return err
-// 	}
-// 	if err = ioutil.WriteFile(ncutils.GetNetclientPathSpecific()+"winsw.exe", input, 0644); err != nil {
-// 		logger.Log(0, "failed to copy winsw.exe to " + ncutils.GetNetclientPath())
-// 		return err
-// 	}
-// 	if err = os.Remove(".\\winsw.exe"); err != nil {
-// 		logger.Log(0, "failed to cleanup local winsw.exe, feel free to delete it")
-// 		return err
-// 	}
-// 	logger.Log(0, "finished copying winsw.exe")
-// 	return nil
-// }
-
-// func downloadWinsw() error {
-// 	fullURLFile := "https://github.com/winsw/winsw/releases/download/v2.11.0/WinSW-x64.exe"
-// 	fileName := "winsw.exe"
-
-// 	// Create the file
-// 	file, err := os.Create(fileName)
-// 	if err != nil {
-// 		logger.Log(0, "could not create file on OS for Winsw")
-// 		return err
-// 	}
-// 	defer file.Close()
-
-// 	client := http.Client{
-// 		CheckRedirect: func(r *http.Request, via []*http.Request) error {
-// 			r.URL.Opaque = r.URL.Path
-// 			return nil
-// 		},
-// 	}
-// 	// Put content on file
-// 	logger.Log(0, "downloading service tool...")
-// 	resp, err := client.Get(fullURLFile)
-// 	if err != nil {
-// 		logger.Log(0, "could not GET Winsw")
-// 		return err
-// 	}
-// 	defer resp.Body.Close()
-
-// 	_, err = io.Copy(file, resp.Body)
-// 	if err != nil {
-// 		logger.Log(0, "could not mount winsw.exe")
-// 		return err
-// 	}
-// 	logger.Log(0, "finished downloading Winsw")
-// 	return nil
-// }

+ 19 - 12
netclient/functions/common.go

@@ -133,6 +133,7 @@ func Uninstall() error {
 			}
 		}
 	}
+	err = nil
 	// clean up OS specific stuff
 	if ncutils.IsWindows() {
 		daemon.CleanupWindows()
@@ -159,19 +160,21 @@ func LeaveNetwork(network string, force bool) error {
 	if node.IsServer != "yes" {
 		token, err := Authenticate(cfg)
 		if err != nil {
-			return fmt.Errorf("unable to authenticate %w", err)
-		}
-		url := "https://" + cfg.Server.API + "/api/nodes/" + cfg.Network + "/" + cfg.Node.ID
-		response, err := API("", http.MethodDelete, url, token)
-		if err != nil {
-			return fmt.Errorf("error deleting node on server %w", err)
-		}
-		if response.StatusCode == http.StatusOK {
-			logger.Log(0, "deleted node", cfg.Node.Name, " on network ", cfg.Network)
+			logger.Log(0, "unable to authenticate: "+err.Error())
 		} else {
-			bodybytes, _ := io.ReadAll(response.Body)
-			defer response.Body.Close()
-			return fmt.Errorf("error deleting node on server %s %s", response.Status, string(bodybytes))
+			url := "https://" + cfg.Server.API + "/api/nodes/" + cfg.Network + "/" + cfg.Node.ID
+			response, err := API("", http.MethodDelete, url, token)
+			if err != nil {
+				logger.Log(0, "error deleting node on server: "+err.Error())
+			} else {
+				if response.StatusCode == http.StatusOK {
+					logger.Log(0, "deleted node", cfg.Node.Name, " on network ", cfg.Network)
+				} else {
+					bodybytes, _ := io.ReadAll(response.Body)
+					defer response.Body.Close()
+					logger.Log(0, fmt.Sprintf("error deleting node on server %s %s", response.Status, string(bodybytes)))
+				}
+			}
 		}
 	}
 	wgClient, wgErr := wgctrl.New()
@@ -301,6 +304,10 @@ func WipeLocal(network string) error {
 			log.Println(err.Error())
 		}
 	}
+	err = removeHostDNS(ifacename, ncutils.IsWindows())
+	if err != nil {
+		logger.Log(0, "failed to delete dns entries for", ifacename, err.Error())
+	}
 	return err
 }
 

+ 27 - 33
netclient/functions/daemon.go

@@ -17,11 +17,12 @@ import (
 	"time"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
-	"github.com/go-ping/ping"
 	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/daemon"
+	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	ssl "github.com/gravitl/netmaker/tls"
@@ -53,10 +54,15 @@ func Daemon() error {
 		cfg.Network = network
 		cfg.ReadConfig()
 		serverSet[cfg.Server.Server] = cfg
-		//temporary code --- remove in version v0.13.0
-		removeHostDNS(network, ncutils.IsWindows())
-		// end of code to be removed in version v0.13.0
-		initialPull(cfg.Network)
+		if err := wireguard.ApplyConf(&cfg.Node, cfg.Node.Interface, ncutils.GetNetclientPathSpecific()+cfg.Node.Interface+".conf"); err != nil {
+			logger.Log(0, "failed to start ", cfg.Node.Interface, "wg interface", err.Error())
+		}
+		//initialPull(cfg.Network)
+	}
+	// set ipforwarding on startup
+	err := local.SetIPForwarding()
+	if err != nil {
+		logger.Log(0, err.Error())
 	}
 
 	// == subscribe to all nodes for each on machine ==
@@ -110,37 +116,17 @@ func UpdateKeys(nodeCfg *config.ClientConfig, client mqtt.Client) error {
 	return nil
 }
 
-// PingServer -- checks if server is reachable
-func PingServer(cfg *config.ClientConfig) error {
-	pinger, err := ping.NewPinger(cfg.Server.Server)
-	if err != nil {
-		return err
-	}
-	pinger.Timeout = 2 * time.Second
-	pinger.Count = 3
-	pinger.Run()
-	stats := pinger.Statistics()
-	if stats.PacketLoss == 100 {
-		return errors.New("ping error " + fmt.Sprintf("%f", stats.PacketLoss))
-	}
-	logger.Log(3, "ping of server", cfg.Server.Server, "was successful")
-	return nil
-}
-
 // == Private ==
 
 // sets MQ client subscriptions for a specific node config
 // should be called for each node belonging to a given server
 func setSubscriptions(client mqtt.Client, nodeCfg *config.ClientConfig) {
-	if nodeCfg.DebugOn {
-		if token := client.Subscribe("#", 0, nil); token.Wait() && token.Error() != nil {
+	if token := client.Subscribe(fmt.Sprintf("update/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID), 0, mqtt.MessageHandler(NodeUpdate)); token.WaitTimeout(mq.MQ_TIMEOUT*time.Second) && token.Error() != nil {
+		if token.Error() == nil {
+			logger.Log(0, "connection timeout")
+		} else {
 			logger.Log(0, token.Error().Error())
-			return
 		}
-		logger.Log(0, "subscribed to all topics for debugging purposes")
-	}
-	if token := client.Subscribe(fmt.Sprintf("update/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID), 0, mqtt.MessageHandler(NodeUpdate)); token.Wait() && token.Error() != nil {
-		logger.Log(0, token.Error().Error())
 		return
 	}
 	logger.Log(3, fmt.Sprintf("subscribed to node updates for node %s update/%s/%s", nodeCfg.Node.Name, nodeCfg.Node.Network, nodeCfg.Node.ID))
@@ -156,12 +142,20 @@ func setSubscriptions(client mqtt.Client, nodeCfg *config.ClientConfig) {
 func unsubscribeNode(client mqtt.Client, nodeCfg *config.ClientConfig) {
 	client.Unsubscribe(fmt.Sprintf("update/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID))
 	var ok = true
-	if token := client.Unsubscribe(fmt.Sprintf("update/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID)); token.Wait() && token.Error() != nil {
-		logger.Log(1, "unable to unsubscribe from updates for node ", nodeCfg.Node.Name, "\n", token.Error().Error())
+	if token := client.Unsubscribe(fmt.Sprintf("update/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID)); token.WaitTimeout(mq.MQ_TIMEOUT*time.Second) && token.Error() != nil {
+		if token.Error() == nil {
+			logger.Log(1, "unable to unsubscribe from updates for node ", nodeCfg.Node.Name, "\n", "connection timeout")
+		} else {
+			logger.Log(1, "unable to unsubscribe from updates for node ", nodeCfg.Node.Name, "\n", token.Error().Error())
+		}
 		ok = false
 	}
-	if token := client.Unsubscribe(fmt.Sprintf("peers/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID)); token.Wait() && token.Error() != nil {
-		logger.Log(1, "unable to unsubscribe from peer updates for node ", nodeCfg.Node.Name, "\n", token.Error().Error())
+	if token := client.Unsubscribe(fmt.Sprintf("peers/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID)); token.WaitTimeout(mq.MQ_TIMEOUT*time.Second) && token.Error() != nil {
+		if token.Error() == nil {
+			logger.Log(1, "unable to unsubscribe from peer updates for node ", nodeCfg.Node.Name, "\n", "connection timeout")
+		} else {
+			logger.Log(1, "unable to unsubscribe from peer updates for node ", nodeCfg.Node.Name, "\n", token.Error().Error())
+		}
 		ok = false
 	}
 	if ok {

+ 3 - 11
netclient/functions/join.go

@@ -108,14 +108,6 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 		}
 	}
 
-	//	if ncutils.IsLinux() {
-	//		_, err := exec.LookPath("resolvectl")
-	//		if err != nil {
-	//			logger.Log("resolvectl not present", 2)
-	//			logger.Log("unable to configure DNS automatically, disabling automated DNS management", 2)
-	//			cfg.Node.DNSOn = "no"
-	//		}
-	//	}
 	if ncutils.IsFreeBSD() {
 		cfg.Node.UDPHolePunch = "no"
 	}
@@ -195,14 +187,14 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 
 	_ = UpdateLocalListenPort(cfg)
 
-	if cfg.Daemon != "off" {
+	if cfg.Daemon == "install" || ncutils.IsFreeBSD() {
 		err = daemon.InstallDaemon(cfg)
 		if err != nil {
 			return err
-		} else {
-			daemon.Restart()
 		}
 	}
+
+	daemon.Restart()
 	return nil
 }
 

+ 4 - 4
netclient/functions/list.go

@@ -38,14 +38,14 @@ type address struct {
 }
 
 // List - lists the current peers for the local node with name and node ID
-func List(network string) error {
+func List(network string) ([]Network, error) {
 	nets := []Network{}
 	var err error
 	var networks []string
 	if network == "all" {
 		networks, err = ncutils.GetSystemNetworks()
 		if err != nil {
-			return err
+			return nil, err
 		}
 	} else {
 		networks = append(networks, network)
@@ -55,7 +55,7 @@ func List(network string) error {
 		net, err := getNetwork(network)
 		if err != nil {
 			logger.Log(1, network+": Could not retrieve network configuration.")
-			return err
+			return nil, err
 		}
 		peers, err := getPeers(network)
 		if err == nil && len(peers) > 0 {
@@ -69,7 +69,7 @@ func List(network string) error {
 	}{nets})
 	fmt.Println(string(jsoncfg))
 
-	return nil
+	return nets, nil
 }
 
 func getNetwork(network string) (Network, error) {

+ 1 - 5
netclient/functions/mqpublish.go

@@ -75,11 +75,7 @@ func Checkin(ctx context.Context, wg *sync.WaitGroup) {
 						}
 					}
 				}
-				if err := PingServer(&nodeCfg); err != nil {
-					logger.Log(0, "could not ping server for", nodeCfg.Network, nodeCfg.Server.Server+"\n", err.Error())
-				} else {
-					Hello(&nodeCfg)
-				}
+				Hello(&nodeCfg)
 				checkCertExpiry(&nodeCfg)
 			}
 		}

+ 33 - 0
netclient/gui/components/buttons.go

@@ -0,0 +1,33 @@
+package components
+
+import (
+	"image/color"
+
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/canvas"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/layout"
+	"fyne.io/fyne/v2/widget"
+)
+
+// ColoredButton - renders a colored button with text
+func ColoredButton(text string, tapped func(), color color.Color) *fyne.Container {
+	btn := widget.NewButton(text, tapped)
+	bgColor := canvas.NewRectangle(color)
+	return container.New(
+		layout.NewMaxLayout(),
+		bgColor,
+		btn,
+	)
+}
+
+// ColoredIconButton - renders a colored button with an icon
+func ColoredIconButton(text string, icon fyne.Resource, tapped func(), color color.Color) *fyne.Container {
+	btn := widget.NewButtonWithIcon(text, icon, tapped)
+	bgColor := canvas.NewRectangle(color)
+	return container.New(
+		layout.NewMaxLayout(),
+		btn,
+		bgColor,
+	)
+}

+ 22 - 0
netclient/gui/components/colors.go

@@ -0,0 +1,22 @@
+package components
+
+import "image/color"
+
+var (
+	// Red_color - preferred red color
+	Red_color = color.NRGBA{R: 233, G: 10, B: 17, A: 155}
+	// Gravitl_color - gravitl primary sea green color
+	Gravitl_color = color.NRGBA{R: 14, G: 173, B: 105, A: 155}
+	// Blue_color - preferred blue color
+	Blue_color = color.NRGBA{R: 17, G: 157, B: 164, A: 155}
+	// Danger_color - preferred danger color
+	Danger_color = color.NRGBA{R: 223, G: 71, B: 89, A: 155}
+	// Purple_color - preferred purple color
+	Purple_color = color.NRGBA{R: 115, G: 80, B: 159, A: 155}
+	// Orange_color - preferred orange color
+	Orange_color = color.NRGBA{R: 253, G: 76, B: 65, A: 155}
+	// Grey_color - preferred grey color
+	Grey_color = color.NRGBA{R: 27, G: 27, B: 27, A: 155}
+	// Gold_color - preferred gold color
+	Gold_color = color.NRGBA{R: 218, G: 165, B: 32, A: 155}
+)

+ 23 - 0
netclient/gui/components/text.go

@@ -0,0 +1,23 @@
+package components
+
+import (
+	"image/color"
+
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/canvas"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/layout"
+	"fyne.io/fyne/v2/widget"
+)
+
+// ColoredText - renders a colored label
+func ColoredText(text string, color color.Color) *fyne.Container {
+	btn := widget.NewLabel(text)
+	btn.Wrapping = fyne.TextWrapWord
+	bgColor := canvas.NewRectangle(color)
+	return container.New(
+		layout.NewMaxLayout(),
+		bgColor,
+		btn,
+	)
+}

+ 39 - 0
netclient/gui/components/toolbar.go

@@ -0,0 +1,39 @@
+package components
+
+import (
+	"image/color"
+
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/widget"
+)
+
+// NewToolbarLabelButton - makes a toolbar button cell with label
+func NewToolbarLabelButton(label string, icon fyne.Resource, onclick func(), colour color.Color) widget.ToolbarItem {
+	l := ColoredIconButton(label, icon, onclick, colour)
+	l.MinSize()
+	return &toolbarLabelButton{l}
+}
+
+// NewToolbarLabel - makes a toolbar text cell
+func NewToolbarLabel(label string) widget.ToolbarItem {
+	l := widget.NewLabel(label)
+	l.MinSize()
+	return &toolbarLabel{l}
+}
+
+type toolbarLabelButton struct {
+	*fyne.Container
+}
+
+type toolbarLabel struct {
+	*widget.Label
+}
+
+func (t *toolbarLabelButton) ToolbarObject() fyne.CanvasObject {
+	return container.NewCenter(t.Container)
+}
+
+func (t *toolbarLabel) ToolbarObject() fyne.CanvasObject {
+	return container.NewCenter(t.Label)
+}

+ 21 - 0
netclient/gui/components/views/confirm.go

@@ -0,0 +1,21 @@
+package views
+
+import (
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/theme"
+	"fyne.io/fyne/v2/widget"
+	"github.com/gravitl/netmaker/netclient/gui/components"
+)
+
+// GetConfirmation - displays a confirmation message
+func GetConfirmation(msg string, onCancel, onConfirm func()) fyne.CanvasObject {
+	return container.NewGridWithColumns(1,
+		container.NewCenter(widget.NewLabel(msg)),
+		container.NewCenter(
+			container.NewHBox(
+				components.ColoredIconButton("Confirm", theme.ConfirmIcon(), onConfirm, components.Gravitl_color),
+				components.ColoredIconButton("Cancel", theme.CancelIcon(), onCancel, components.Danger_color),
+			)),
+	)
+}

+ 25 - 0
netclient/gui/components/views/content.go

@@ -0,0 +1,25 @@
+package views
+
+import (
+	"fyne.io/fyne/v2"
+)
+
+// CurrentContent - the content currently being displayed
+var CurrentContent *fyne.Container
+
+// RemoveContent - removes a rendered content
+func RemoveContent(name string) {
+	CurrentContent.Remove(GetView(name))
+}
+
+// AddContent - adds content to be rendered
+func AddContent(name string) {
+	CurrentContent.Add(GetView(name))
+}
+
+// RefreshComponent - refreshes the component to re-render
+func RefreshComponent(name string, c fyne.CanvasObject) {
+	RemoveContent(name)
+	SetView(name, c)
+	AddContent(name)
+}

+ 62 - 0
netclient/gui/components/views/join.go

@@ -0,0 +1,62 @@
+package views
+
+import (
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/theme"
+	"fyne.io/fyne/v2/widget"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/functions"
+	"github.com/gravitl/netmaker/netclient/gui/components"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+// GetJoinView - get's the join screen where a user inputs an access token
+func GetJoinView() fyne.CanvasObject {
+
+	input := widget.NewMultiLineEntry()
+	input.SetPlaceHolder("access token here...")
+
+	submitBtn := components.ColoredIconButton("Submit", theme.UploadIcon(), func() {
+		// ErrorNotify("Could not process token")
+		LoadingNotify()
+		var cfg config.ClientConfig
+		accesstoken, err := config.ParseAccessToken(input.Text)
+		if err != nil {
+			ErrorNotify("Failed to parse access token!")
+			return
+		}
+		cfg.Network = accesstoken.ClientConfig.Network
+		cfg.Node.Network = accesstoken.ClientConfig.Network
+		cfg.Node.Name = ncutils.GetHostname()
+		cfg.Server.AccessKey = accesstoken.ClientConfig.Key
+		cfg.Node.LocalRange = accesstoken.ClientConfig.LocalRange
+		cfg.Server.Server = accesstoken.ServerConfig.Server
+		cfg.Server.API = accesstoken.ServerConfig.APIConnString
+		err = functions.JoinNetwork(&cfg, "")
+		if err != nil {
+			ErrorNotify("Failed to join " + cfg.Network + "!")
+			return
+		}
+		networks, err := ncutils.GetSystemNetworks()
+		if err != nil {
+			ErrorNotify("Failed to read local networks!")
+			return
+		}
+		SuccessNotify("Joined " + cfg.Network + "!")
+		input.Text = ""
+		RefreshComponent(Networks, GetNetworksView(networks))
+		ShowView(Networks)
+		// TODO
+		// - call join
+		// - display loading
+		// - on error display error notification
+		// - on success notify success, refresh networks & networks view, display networks view
+	}, components.Blue_color)
+
+	return container.NewGridWithColumns(1,
+		container.NewCenter(widget.NewLabel("Join new network with Access Token")),
+		input,
+		container.NewCenter(submitBtn),
+	)
+}

+ 167 - 0
netclient/gui/components/views/networks.go

@@ -0,0 +1,167 @@
+package views
+
+import (
+	"fmt"
+	"time"
+
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/layout"
+	"fyne.io/fyne/v2/theme"
+	"fyne.io/fyne/v2/widget"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/functions"
+	"github.com/gravitl/netmaker/netclient/gui/components"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+var currentNetwork *string
+
+// GetNetworksView - displays the view of all networks
+func GetNetworksView(networks []string) fyne.CanvasObject {
+	// renders := []fyne.CanvasObject{}
+	if networks == nil || len(networks) == 0 {
+		return container.NewCenter(widget.NewLabel("No networks present"))
+	}
+	grid := container.New(layout.NewGridLayout(4),
+		container.NewCenter(widget.NewLabel("Network Name")),
+		container.NewCenter(widget.NewLabel("Node Info")),
+		container.NewCenter(widget.NewLabel("Pull Latest")),
+		container.NewCenter(widget.NewLabel("Leave network")),
+	)
+	for i := range networks {
+		network := &networks[i]
+		grid.AddObject(
+			container.NewCenter(widget.NewLabel(*network)),
+		)
+		grid.AddObject(
+			components.ColoredIconButton("info", theme.InfoIcon(), func() {
+				RefreshComponent(NetDetails, GetSingleNetworkView(*network))
+				ShowView(NetDetails)
+			}, components.Gold_color),
+		)
+		grid.AddObject(
+			components.ColoredIconButton("pull", theme.DownloadIcon(), func() {
+				// TODO call pull with network name
+				pull(*network)
+			}, components.Blue_color),
+		)
+		grid.AddObject(
+			components.ColoredIconButton("leave", theme.DeleteIcon(), func() {
+				leave(*network)
+			}, components.Danger_color),
+		)
+		// renders = append(renders, container.NewCenter(netToolbar))
+	}
+
+	return container.NewCenter(grid)
+}
+
+// GetSingleNetworkView - returns details and option to pull a network
+func GetSingleNetworkView(network string) fyne.CanvasObject {
+	if network == "" || len(network) == 0 {
+		return container.NewCenter(widget.NewLabel("No valid network selected"))
+	}
+
+	// == read node values ==
+	LoadingNotify()
+	nets, err := functions.List(network)
+	if err != nil || len(nets) < 1 {
+		return container.NewCenter(widget.NewLabel("No data retrieved."))
+	}
+	var nodecfg config.ClientConfig
+	nodecfg.Network = network
+	nodecfg.ReadConfig()
+	nodeID := nodecfg.Node.ID
+	lastCheckInTime := time.Unix(nodecfg.Node.LastCheckIn, 0)
+	lastCheckIn := lastCheckInTime.Format("2006-01-02 15:04:05")
+	privateAddr := nodecfg.Node.Address
+	privateAddr6 := nodecfg.Node.Address6
+	endpoint := nodecfg.Node.Endpoint
+	health := " (HEALTHY)"
+	if time.Now().After(lastCheckInTime.Add(time.Minute * 5)) {
+		health = " (WARNING)"
+	} else if time.Now().After(lastCheckInTime.Add(time.Minute * 30)) {
+		health = " (ERROR)"
+	}
+	lastCheckIn += health
+	version := nodecfg.Node.Version
+
+	pullBtn := components.ColoredButton("pull "+network, func() { pull(network) }, components.Blue_color)
+	pullBtn.Resize(fyne.NewSize(pullBtn.Size().Width, 50))
+
+	view := container.NewGridWithColumns(1, widget.NewRichTextFromMarkdown(fmt.Sprintf(`### %s
+- ID: %s
+- Last Check In: %s
+- Endpoint: %s
+- Address (IPv4): %s
+- Address6 (IPv6): %s
+- Version: %s
+### Peers
+	`, network, nodeID, lastCheckIn, endpoint, privateAddr, privateAddr6, version)),
+	)
+	netDetailsView := container.NewCenter(
+		view,
+	)
+
+	peerView := container.NewVBox()
+
+	for _, p := range nets[0].Peers {
+		peerString := ""
+		endpointEntry := widget.NewEntry()
+		endpointEntry.Text = fmt.Sprintf("Endpoint: %s", p.PublicEndpoint)
+		endpointEntry.Disable()
+		newEntry := widget.NewEntry()
+		for i, addr := range p.Addresses {
+			if i > 0 && i < len(p.Addresses) {
+				peerString += ", "
+			}
+			peerString += fmt.Sprintf("%s", addr.IP)
+		}
+		newEntry.Text = peerString
+		newEntry.Disable()
+		peerView.AddObject(widget.NewLabel(fmt.Sprintf("Peer: %s", p.PublicKey)))
+		peerView.AddObject(container.NewVBox(container.NewVBox(endpointEntry), container.NewVBox(newEntry)))
+	}
+	peerScroller := container.NewVScroll(peerView)
+	view.AddObject(peerScroller)
+	view.AddObject(container.NewVBox(pullBtn))
+	netDetailsView.Refresh()
+	ClearNotification()
+	return netDetailsView
+}
+
+// == private ==
+func pull(network string) {
+	LoadingNotify()
+	_, err := functions.Pull(network, true)
+	if err != nil {
+		ErrorNotify("Failed to pull " + network + " : " + err.Error())
+	} else {
+		SuccessNotify("Pulled " + network + "!")
+	}
+}
+
+func leave(network string) {
+
+	confirmView := GetConfirmation("Confirm leaving "+network+"?", func() {
+		ShowView(Networks)
+	}, func() {
+		LoadingNotify()
+		err := functions.LeaveNetwork(network, true)
+		if err != nil {
+			ErrorNotify("Failed to leave " + network + " : " + err.Error())
+		} else {
+			SuccessNotify("Left " + network)
+		}
+		networks, err := ncutils.GetSystemNetworks()
+		if err != nil {
+			networks = []string{}
+			ErrorNotify("Failed to read local networks!")
+		}
+		RefreshComponent(Networks, GetNetworksView(networks))
+		ShowView(Networks)
+	})
+	RefreshComponent(Confirm, confirmView)
+	ShowView(Confirm)
+}

+ 38 - 0
netclient/gui/components/views/notification.go

@@ -0,0 +1,38 @@
+package views
+
+import (
+	"image/color"
+
+	"fyne.io/fyne/v2"
+	"github.com/gravitl/netmaker/netclient/gui/components"
+)
+
+// GenerateNotification - generates a notification
+func GenerateNotification(text string, c color.Color) fyne.CanvasObject {
+	return components.ColoredText(text, c)
+}
+
+// ChangeNotification - changes the current notification in the view
+func ChangeNotification(text string, c color.Color) {
+	RefreshComponent(Notify, GenerateNotification(text, c))
+}
+
+// ClearNotification - hides the notifications
+func ClearNotification() {
+	RefreshComponent(Notify, GenerateNotification("", color.Transparent))
+}
+
+// LoadingNotify - changes notification to loading...
+func LoadingNotify() {
+	RefreshComponent(Notify, GenerateNotification("loading...", components.Blue_color))
+}
+
+// ErrorNotify - changes notification to a specified error
+func ErrorNotify(msg string) {
+	RefreshComponent(Notify, GenerateNotification(msg, components.Danger_color))
+}
+
+// SuccessNotify - changes notification to a specified success message
+func SuccessNotify(msg string) {
+	RefreshComponent(Notify, GenerateNotification(msg, components.Gravitl_color))
+}

+ 44 - 0
netclient/gui/components/views/state.go

@@ -0,0 +1,44 @@
+package views
+
+import (
+	"fyne.io/fyne/v2"
+)
+
+var (
+	// Views - the map of all the view components
+	views = make(map[string]fyne.CanvasObject)
+)
+
+const (
+	Networks   = "networks"
+	NetDetails = "netdetails"
+	Notify     = "notification"
+	Join       = "join"
+	Confirm    = "confirm"
+)
+
+// GetView - returns the requested view and sets the CurrentView state
+func GetView(viewName string) fyne.CanvasObject {
+	return views[viewName]
+}
+
+// SetView - sets a view in the views map
+func SetView(viewName string, component fyne.CanvasObject) {
+	views[viewName] = component
+}
+
+// HideView - hides a specific view
+func HideView(viewName string) {
+	views[viewName].Hide()
+}
+
+// ShowView - show's a specific view
+func ShowView(viewName string) {
+	for k := range views {
+		if k == Notify {
+			continue
+		}
+		HideView(k)
+	}
+	views[viewName].Show()
+}

+ 111 - 0
netclient/gui/gui.go

@@ -0,0 +1,111 @@
+package gui
+
+import (
+	"embed"
+	"image/color"
+	"os"
+
+	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/app"
+	"fyne.io/fyne/v2/container"
+	"fyne.io/fyne/v2/theme"
+	"fyne.io/fyne/v2/widget"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/netclient/functions"
+	"github.com/gravitl/netmaker/netclient/gui/components"
+	"github.com/gravitl/netmaker/netclient/gui/components/views"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+//go:embed nm-logo-sm.png
+var logoContent embed.FS
+
+// Run - run's the netclient GUI
+func Run(networks []string) error {
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Log(0, "No monitor detected, please use CLI commands; use -help for more info.")
+		}
+	}()
+	a := app.New()
+	window := a.NewWindow("Netclient - " + ncutils.Version)
+
+	img, err := logoContent.ReadFile("nm-logo-sm.png")
+	if err != nil {
+		logger.Log(0, "failed to read logo", err.Error())
+		return err
+	}
+
+	window.SetIcon(&fyne.StaticResource{StaticName: "Netmaker logo", StaticContent: img})
+	window.Resize(fyne.NewSize(600, 450))
+
+	networkView := container.NewVScroll(views.GetNetworksView(networks))
+	networkView.SetMinSize(fyne.NewSize(400, 300))
+	views.SetView(views.Networks, networkView)
+
+	netDetailsViews := container.NewVScroll(views.GetSingleNetworkView(""))
+	netDetailsViews.SetMinSize(fyne.NewSize(400, 300))
+	views.SetView(views.NetDetails, netDetailsViews)
+	window.SetFixedSize(false)
+
+	toolbar := container.NewCenter(widget.NewToolbar(
+		components.NewToolbarLabelButton("Networks", theme.HomeIcon(), func() {
+			views.ShowView(views.Networks)
+			views.ClearNotification()
+		}, components.Blue_color),
+		components.NewToolbarLabelButton("Join new", theme.ContentAddIcon(), func() {
+			views.ShowView(views.Join)
+		}, components.Gravitl_color),
+		components.NewToolbarLabelButton("Uninstall", theme.ErrorIcon(), func() {
+			confirmView := views.GetConfirmation("Confirm Netclient uninstall?", func() {
+				views.ShowView(views.Networks)
+			}, func() {
+				views.LoadingNotify()
+				err := functions.Uninstall()
+				if err != nil {
+					views.ErrorNotify("Failed to uninstall: \n" + err.Error())
+				} else {
+					views.SuccessNotify("Uninstalled Netclient!")
+				}
+				networks, err := ncutils.GetSystemNetworks()
+				if err != nil {
+					networks = []string{}
+				}
+				views.RefreshComponent(views.Networks, views.GetNetworksView(networks))
+				views.ShowView(views.Networks)
+			})
+			views.RefreshComponent(views.Confirm, confirmView)
+			views.ShowView(views.Confirm)
+		}, components.Red_color),
+		components.NewToolbarLabelButton("Close", theme.ContentClearIcon(), func() {
+			os.Exit(0)
+		}, components.Purple_color),
+	))
+
+	joinView := views.GetJoinView()
+	views.SetView(views.Join, joinView)
+
+	confirmView := views.GetConfirmation("", func() {}, func() {})
+	views.SetView(views.Confirm, confirmView)
+
+	views.ShowView(views.Networks)
+
+	initialNotification := views.GenerateNotification("", color.Transparent)
+	views.SetView(views.Notify, initialNotification)
+
+	views.CurrentContent = container.NewVBox()
+
+	views.CurrentContent.Add(container.NewGridWithRows(
+		1,
+		toolbar,
+	))
+	views.CurrentContent.Add(views.GetView(views.Networks))
+	views.CurrentContent.Add(views.GetView(views.NetDetails))
+	views.CurrentContent.Add(views.GetView(views.Notify))
+	views.CurrentContent.Add(views.GetView(views.Join))
+
+	window.SetContent(views.CurrentContent)
+	window.ShowAndRun()
+
+	return nil
+}

BIN
netclient/gui/nm-logo-sm.png


+ 19 - 0
netclient/local/local.go

@@ -32,6 +32,8 @@ func SetIPForwarding() error {
 
 // SetIPForwardingLinux - sets the ipforwarding for linux
 func SetIPForwardingUnix() error {
+
+	// ipv4
 	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.")
@@ -46,6 +48,23 @@ func SetIPForwardingUnix() error {
 			}
 		}
 	}
+
+	// ipv6
+	out, err = ncutils.RunCmd("sysctl net.ipv6.conf.all.forwarding", true)
+	if err != nil {
+		log.Println("WARNING: Error encountered setting ipv6 forwarding. This can break functionality.")
+		return err
+	} else {
+		s := strings.Fields(string(out))
+		if s[2] != "1" {
+			_, err = ncutils.RunCmd("sysctl -w  net.ipv6.conf.all.forwarding=1", true)
+			if err != nil {
+				log.Println("WARNING: Error encountered setting ipv6 forwarding. You may want to investigate this.")
+				return err
+			}
+		}
+	}
+
 	return nil
 }
 

+ 10 - 4
netclient/main.go

@@ -1,4 +1,5 @@
-//go:generate goversioninfo -icon=windowsdata/resource/netmaker.ico -manifest=netclient.exe.manifest.xml -64=true -o=netclient.syso
+//go:generate goversioninfo -icon=windowsdata/resource/netclient.ico -manifest=netclient.exe.manifest.xml -64=true -o=netclient.syso
+// -build gui
 
 package main
 
@@ -8,6 +9,7 @@ import (
 	"runtime/debug"
 
 	"github.com/gravitl/netmaker/netclient/cli_options"
+	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncwindows"
 	"github.com/urfave/cli/v2"
@@ -35,9 +37,13 @@ func main() {
 		ncutils.CheckWG()
 	}
 
-	err := app.Run(os.Args)
-	if err != nil {
-		log.Fatal(err)
+	if len(os.Args) <= 1 && config.GuiActive {
+		config.GuiRun.(func())()
+	} else {
+		err := app.Run(os.Args)
+		if err != nil {
+			log.Fatal(err)
+		}
 	}
 }
 

+ 22 - 0
netclient/main_gui.go

@@ -0,0 +1,22 @@
+//go:build gui
+// +build gui
+
+package main
+
+import (
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/gui"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+func init() {
+	config.GuiActive = true
+
+	config.GuiRun = func() {
+		networks, err := ncutils.GetSystemNetworks()
+		if err != nil {
+			networks = []string{}
+		}
+		gui.Run(networks)
+	}
+}

+ 9 - 4
netclient/ncutils/netclientutils.go

@@ -24,8 +24,10 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
-// Version - version of the netclient
-var Version = "dev"
+var (
+	// Version - version of the netclient
+	Version = "dev"
+)
 
 // MAX_NAME_LENGTH - maximum node name length
 const MAX_NAME_LENGTH = 62
@@ -40,7 +42,7 @@ const NO_DB_RECORDS = "could not find any records"
 const LINUX_APP_DATA_PATH = "/etc/netclient"
 
 // WINDOWS_APP_DATA_PATH - windows path
-const WINDOWS_APP_DATA_PATH = "C:\\ProgramData\\Netclient"
+const WINDOWS_APP_DATA_PATH = "C:\\Program Files (x86)\\Netclient"
 
 // WINDOWS_APP_DATA_PATH - windows path
 //const WINDOWS_WG_DPAPI_PATH = "C:\\Program Files\\WireGuard\\Data\\Configurations"
@@ -128,7 +130,10 @@ func GetPublicIP() (string, error) {
 	endpoint := ""
 	var err error
 	for _, ipserver := range iplist {
-		resp, err := http.Get(ipserver)
+		client := &http.Client{
+			Timeout: time.Second * 10,
+		}
+		resp, err := client.Get(ipserver)
 		if err != nil {
 			continue
 		}

+ 1 - 1
netclient/netclient.exe.manifest.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
     <assemblyIdentity
-            version="0.13.1.0"
+            version="0.14.0.0"
             processorArchitecture="*"
             name="netclient.exe"
             type="win32"

+ 3 - 3
netclient/versioninfo.json

@@ -2,13 +2,13 @@
     "FixedFileInfo": {
         "FileVersion": {
             "Major": 0,
-            "Minor": 9,
+            "Minor": 14,
             "Patch": 0,
             "Build": 0
         },
         "ProductVersion": {
             "Major": 0,
-            "Minor": 9,
+            "Minor": 14,
             "Patch": 0,
             "Build": 0
         },
@@ -29,7 +29,7 @@
         "OriginalFilename": "",
         "PrivateBuild": "",
         "ProductName": "Netclient",
-        "ProductVersion": "v0.13.1.0",
+        "ProductVersion": "v0.14.0.0",
         "SpecialBuild": ""
     },
     "VarFileInfo": {

BIN
netclient/windowsdata/resource/netclient.ico


BIN
netclient/windowsdata/resource/netmaker.ico


+ 26 - 18
netclient/wireguard/common.go

@@ -58,7 +58,9 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 				iparr = append(iparr, ipaddr.String())
 			}
 		}
-		allowedips = strings.Join(iparr, ",")
+		if iparr != nil && len(iparr) > 0 {
+			allowedips = strings.Join(iparr, ",")
+		}
 		keepAliveString := strconv.Itoa(int(keepalive))
 		if keepAliveString == "0" {
 			keepAliveString = "15"
@@ -78,30 +80,36 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 		}
 	}
 
-	for _, currentPeer := range devicePeers {
-		shouldDelete := true
-		for _, peer := range peers {
-			if peer.AllowedIPs[0].String() == currentPeer.AllowedIPs[0].String() {
-				shouldDelete = false
-			}
-			// re-check this if logic is not working, added in case of allowedips not working
-			if peer.PublicKey.String() == currentPeer.PublicKey.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())
+	if devicePeers != nil && len(devicePeers) > 0 {
+		for _, currentPeer := range devicePeers {
+			shouldDelete := true
+			if peers != nil && len(peers) > 0 {
+				for _, peer := range peers {
+					if peer.AllowedIPs[0].String() == currentPeer.AllowedIPs[0].String() {
+						shouldDelete = false
+					}
+					// re-check this if logic is not working, added in case of allowedips not working
+					if peer.PublicKey.String() == currentPeer.PublicKey.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())
+					}
+				}
+				oldPeerAllowedIps[currentPeer.PublicKey.String()] = currentPeer.AllowedIPs
 			}
 		}
-		oldPeerAllowedIps[currentPeer.PublicKey.String()] = currentPeer.AllowedIPs
 	}
 	if ncutils.IsMac() {
 		err = SetMacPeerRoutes(iface)
 		return err
 	} else if ncutils.IsLinux() {
-		local.SetPeerRoutes(iface, oldPeerAllowedIps, peers)
+		if peers != nil && len(peers) > 0 {
+			local.SetPeerRoutes(iface, oldPeerAllowedIps, peers)
+		}
 	}
 
 	return nil

BIN
netmaker.exe


+ 15 - 20
scripts/nm-quick.sh

@@ -174,36 +174,31 @@ echo "visit dashboard.$NETMAKER_BASE_DOMAIN to log in"
 sleep 2
 
 setup_mesh() {
-echo "creating default network (10.101.0.0/16)"
+echo "creating netmaker network (10.101.0.0/16)"
 
-curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"default"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/networks
+curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"netmaker"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
 
 sleep 2
 
-echo "creating default key"
+echo "creating netmaker access key"
 
-curlresponse=$(curl -s -d '{"uses":99999,"name":"defaultkey"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/networks/default/keys)
+curlresponse=$(curl -s -d '{"uses":99999,"name":"netmaker-key"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks/netmaker/keys)
 ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
 
 sleep 2
 
 echo "configuring netmaker server as ingress gateway"
 
-curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/nodes/default)
+curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker)
 SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
 
-curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/nodes/default/$SERVER_ID/createingress
+curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$SERVER_ID/createingress
 
 echo "finished configuring server and network. You can now add clients."
 echo ""
-echo "For Linux and Mac clients, install with the following command:"
-echo "        curl -sfL https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/netclient-install.sh | sudo KEY=$ACCESS_TOKEN sh -"
-echo ""
-echo "For Windows clients, perform the following from powershell, as administrator:"
-echo "        1. Make sure WireGuardNT is installed - https://download.wireguard.com/windows-client/wireguard-installer.exe"
-echo "        2. Download netclient.exe - wget https://github.com/gravitl/netmaker/releases/download/latest/netclient.exe"
-echo "        3. Install Netclient - powershell.exe .\\netclient.exe join -t $ACCESS_TOKEN"
-echo "        4. Whitelist C:\ProgramData\Netclient in Windows Defender"
+echo "For Linux, Mac, Windows, and FreeBSD:"
+echo "        1. Install the netclient: https://docs.netmaker.org/netclient.html#installation"
+echo "        2. Join the network: netclient join -t $ACCESS_TOKEN"
 echo ""
 echo "For Android and iOS clients, perform the following steps:"
 echo "        1. Log into UI at dashboard.$NETMAKER_BASE_DOMAIN"
@@ -217,16 +212,16 @@ echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
 setup_vpn() {
 echo "creating vpn network (10.201.0.0/16)"
 
-curl -s -o /dev/null -d '{"addressrange":"10.201.0.0/16","netid":"vpn","defaultextclientdns":"8.8.8.8"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/networks
+curl -s -o /dev/null -d '{"addressrange":"10.201.0.0/16","netid":"vpn","defaultextclientdns":"8.8.8.8"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
 
 sleep 2
 
 echo "configuring netmaker server as vpn inlet..."
 
-curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/nodes/vpn)
+curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn)
 SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
 
-curl -s -o /dev/null -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/nodes/vpn/$SERVER_ID/createingress
+curl -s -o /dev/null -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/createingress
 
 echo "waiting 10 seconds for server to apply configuration..."
 
@@ -239,7 +234,7 @@ echo "configuring netmaker server vpn gateway..."
 
 echo "gateway iface: $GATEWAY_IFACE"
 
-curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/nodes/vpn)
+curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn)
 SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
 
 EGRESS_JSON=$( jq -n \
@@ -248,7 +243,7 @@ EGRESS_JSON=$( jq -n \
 
 
 echo "egress json: $EGRESS_JSON"
-curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/nodes/vpn/$SERVER_ID/creategateway
+curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/creategateway
 
 echo "creating client configs..."
 
@@ -258,7 +253,7 @@ do
                   --arg clientid "vpnclient-$a" \
                   '{clientid: $clientid}' )
 
-        curl -s -o /dev/null -d "$CLIENT_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' localhost:8081/api/extclients/vpn/$SERVER_ID
+        curl -s -o /dev/null -d "$CLIENT_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/extclients/vpn/$SERVER_ID
 done
 
 echo "finished configuring vpn server."

+ 5 - 7
servercfg/serverconf.go

@@ -7,6 +7,7 @@ import (
 	"os"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/gravitl/netmaker/config"
 )
@@ -78,7 +79,6 @@ func GetServerConfig() config.ServerConfig {
 	} else {
 		cfg.RCE = "off"
 	}
-	cfg.Debug = GetDebug()
 	cfg.Telemetry = Telemetry()
 	cfg.ManageIPTables = ManageIPTables()
 	services := strings.Join(GetPortForwardServiceList(), ",")
@@ -412,7 +412,10 @@ func GetPublicIP() (string, error) {
 
 	iplist := []string{"https://ip.server.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
 	for _, ipserver := range iplist {
-		resp, err := http.Get(ipserver)
+		client := &http.Client{
+			Timeout: time.Second * 10,
+		}
+		resp, err := client.Get(ipserver)
 		if err != nil {
 			continue
 		}
@@ -546,8 +549,3 @@ func GetAzureTenant() string {
 func GetRce() bool {
 	return os.Getenv("RCE") == "on" || config.Config.Server.RCE == "on"
 }
-
-// GetDebug -- checks if debugging is enabled, off by default
-func GetDebug() bool {
-	return os.Getenv("DEBUG") == "on" || config.Config.Server.Debug == true
-}