Browse Source

Merge branch 'develop' into story/GRA-1147

Alex Feiszli 2 years ago
parent
commit
35f13bc341
100 changed files with 2562 additions and 2527 deletions
  1. 4 2
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 15 0
      .github/pull_request_template.md
  3. 0 73
      .github/workflows/buildandrelease.yml
  4. 3 2
      .github/workflows/docker-builder.yml
  5. 46 0
      .github/workflows/packages.yml
  6. 5 4
      .github/workflows/publish-docker.yml
  7. 32 0
      .github/workflows/pull-request.yml
  8. 0 29
      .github/workflows/purgeGHCR.yml
  9. 50 0
      .github/workflows/release-assets.yml
  10. 39 0
      .github/workflows/release-branch.yml
  11. 49 0
      .github/workflows/release.yml
  12. 1 0
      .github/workflows/test.yml
  13. 41 0
      .github/workflows/upgraderelease.yml
  14. 1 0
      .gitignore
  15. 34 0
      .goreleaser.prerelease.yaml
  16. 4 0
      .goreleaser.update.yaml
  17. 34 0
      .goreleaser.yaml
  18. 1 6
      Dockerfile
  19. 2 2
      README.md
  20. 20 0
      cli/cmd/host/add_network.go
  21. 20 0
      cli/cmd/host/delete_network.go
  22. 0 22
      cli/cmd/host/update_networks.go
  23. 0 4
      cli/cmd/network/create.go
  24. 0 1
      cli/cmd/network/flags.go
  25. 0 4
      cli/cmd/network/update.go
  26. 8 5
      cli/functions/host.go
  27. 0 2
      cli/samples/network.json
  28. 0 4
      cli/samples/node.json
  29. 83 0
      compose/docker-compose-emqx.yml
  30. 19 24
      compose/docker-compose.ee.yml
  31. 16 12
      compose/docker-compose.reference.yml
  32. 5 9
      compose/docker-compose.yml
  33. 4 7
      config/config.go
  34. 4 4
      config/config_test.go
  35. 0 1
      config/environments/dev.yaml
  36. 1 2
      controllers/config/environments/dev.yaml
  37. 5 11
      controllers/controller.go
  38. 18 3
      controllers/dns.go
  39. 29 40
      controllers/dns_test.go
  40. 1 1
      controllers/docs.go
  41. 238 0
      controllers/enrollmentkeys.go
  42. 28 12
      controllers/ext_client.go
  43. 28 8
      controllers/hosts.go
  44. 17 5
      controllers/network.go
  45. 22 24
      controllers/network_test.go
  46. 58 25
      controllers/node.go
  47. 0 3
      controllers/node_test.go
  48. 62 49
      controllers/user_test.go
  49. 3 0
      database/database.go
  50. 0 7
      dev.yaml
  51. 1 1
      docker/Caddyfile
  52. 2 1
      docker/Caddyfile-EE
  53. 22 8
      functions/helpers_test.go
  54. 9 10
      go.mod
  55. 20 34
      go.sum
  56. 1 1
      k8s/client/netclient-daemonset.yaml
  57. 1 1
      k8s/client/netclient.yaml
  58. 5 7
      k8s/server/netmaker-server.yaml
  59. 1 1
      k8s/server/netmaker-ui.yaml
  60. 0 2
      logic/accesskeys_test.go
  61. 221 0
      logic/enrollmentkey.go
  62. 206 0
      logic/enrollmentkey_test.go
  63. 12 0
      logic/host_test.go
  64. 38 0
      logic/hostactions/hostactions.go
  65. 9 17
      logic/hosts.go
  66. 0 37
      logic/metrics/metrics.go
  67. 1 47
      logic/nodes.go
  68. 96 649
      logic/peers.go
  69. 5 1
      logic/pro/networkuser_test.go
  70. 0 2
      logic/pro/usergroups_test.go
  71. 60 26
      logic/zombie.go
  72. 27 40
      main.go
  73. 9 2
      models/api_host.go
  74. 0 3
      models/api_node.go
  75. 39 0
      models/dnsEntry.go
  76. 59 0
      models/enrollment_key.go
  77. 4 0
      models/host.go
  78. 13 27
      models/mqtt.go
  79. 0 4
      models/network.go
  80. 1 1
      models/network_test.go
  81. 0 14
      models/node.go
  82. 1 1
      models/structs.go
  83. 154 0
      mq/emqx.go
  84. 23 8
      mq/handlers.go
  85. 17 0
      mq/mq.go
  86. 299 85
      mq/publishers.go
  87. 0 9
      netclient/global_settings/globalsettings.go
  88. 0 78
      netclient/ncutils/iface.go
  89. 0 593
      netclient/ncutils/netclientutils.go
  90. 0 39
      netclient/ncutils/netclientutils_darwin.go
  91. 0 50
      netclient/ncutils/netclientutils_freebsd.go
  92. 0 32
      netclient/ncutils/netclientutils_linux.go
  93. 0 61
      netclient/ncutils/netclientutils_windows.go
  94. 0 97
      netclient/ncutils/peerhelper.go
  95. 0 46
      netclient/ncutils/pid.go
  96. 0 25
      netclient/ncutils/util.go
  97. BIN
      netclient/ncutils/windowsdaemon/winsw.exe
  98. 7 0
      release.md
  99. 146 55
      scripts/nm-quick.sh
  100. 3 5
      scripts/nm-upgrade.sh

+ 4 - 2
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -31,8 +31,10 @@ body:
       label: Version
       label: Version
       description: What version are you running?
       description: What version are you running?
       options:
       options:
-        - v0.18.0      
-        - v0.17.1      
+        - v0.18.2
+        - v0.18.1
+        - v0.18.0
+        - v0.17.1
         - v0.17.0
         - v0.17.0
         - v0.16.3
         - v0.16.3
         - v0.16.2
         - v0.16.2

+ 15 - 0
.github/pull_request_template.md

@@ -0,0 +1,15 @@
+## Describe your changes
+
+## Provide Issue ticket number if applicable/not in title
+
+## Provide testing steps
+
+## Checklist before requesting a review
+- [ ] My changes affect only 10 files or less.
+- [ ] I have performed a self-review of my code and tested it.
+- [ ] If it is a new feature, I have added thorough tests, my code is <= 1450 lines.
+- [ ] If it is a bugfix, my code is <= 200 lines.
+- [ ] My functions are <= 80 lines.
+- [ ] I have had my code reviewed by a peer.
+- [ ] My unit tests pass locally.
+- [ ] Netmaker is awesome.

+ 0 - 73
.github/workflows/buildandrelease.yml

@@ -1,73 +0,0 @@
-name: Build and Release
-
-on:
-  workflow_dispatch:
-    inputs:
-      version:
-        description: 'Netmaker version'
-        required: true
-  release:
-    types: [published]
-
-jobs:
-  version:
-    runs-on: ubuntu-latest
-    outputs:
-      tag: ${{ steps.echo.outputs.tag }}
-      version: ${{ steps.echo.outputs.version }}
-    steps:
-      - name: Get Version Number
-        run: |
-          if [[ -n "${{ github.event.inputs.version }}" ]]; then
-            NETMAKER_VERSION=${{ github.event.inputs.version }}
-          else
-            NETMAKER_VERSION=$(curl -fsSL https://api.github.com/repos/gravitl/netmaker/tags | grep 'name' | head -1 | cut -d'"' -f4)
-          fi
-          echo "NETMAKER_VERSION=${NETMAKER_VERSION}" >> $GITHUB_ENV
-          # remove everything but digits and . for package (deb, rpm, etc) versions
-          PACKAGE_VERSION=$(echo ${NETMAKER_VERSION} | tr -cd '[:digit:].')
-          echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> $GITHUB_ENV
-      - name: Echo
-        id: echo
-        run: |
-          echo ${{ env.NETMAKER_VERSION }}
-          echo ${{ env.PACKAGE_VERSION }}
-          if [[ -z ${{ env.NETMAKER_VERSION }} || -z ${{ env.PACKAGE_VERSION }} ]]
-          then
-            exit 1
-          fi
-          echo "::set-output name=tag::${{ env.NETMAKER_VERSION }}"
-          echo "::set-output name=version::${{ env.PACKAGE_VERSION }}"
-  netmaker:
-    runs-on: ubuntu-latest
-    needs: version
-    steps:
-      - name: set variables
-        run: |
-          echo ${{ needs.version.outputs.tag }} ${{ needs.version.outputs.version }}
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          if [[ -z ${VERSION} || -z ${TAG} ]]; then
-            exit 1
-          fi
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-          env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netmaker main.go
-      - name: Upload netmaker x86 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: build/netmaker
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netmaker
-

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

@@ -2,8 +2,9 @@ name: Build go-builder images
 
 
 on:
 on:
   workflow_dispatch:
   workflow_dispatch:
-  schedule:
-    - cron: '00 21 * * SUN'
+  push:
+    branches:
+      - 'develop'
 
 
 jobs:
 jobs:
   go-builder:
   go-builder:

+ 46 - 0
.github/workflows/packages.yml

@@ -0,0 +1,46 @@
+name: Build linux packages
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+  release:
+    types: [released]
+
+jobs:
+
+  packages:
+    runs-on: ubuntu-latest
+    steps:
+      - name: setup ssh
+        run: |
+          mkdir -p ~/.ssh/
+          echo "$SSH_KEY" > ~/.ssh/id_devops
+          chmod 600 ~/.ssh/id_devops
+          cat >>~/.ssh/config <<END
+          Host *.clustercat.com
+            User root
+            IdentityFile ~/.ssh/id_devops
+            StrictHostKeyChecking no
+          END
+        env:
+          SSH_KEY: ${{ secrets.TESTING_SSH_KEY }}
+      - name: set version
+        run: |
+          if [[ -n "${{ github.event.inputs.version }}" ]]; then
+            # remove everything but digits and . for package (deb, rpm, etc) versions
+            VERSION=$(echo ${{ github.event.inputs.version }} | tr -cd '[:digit:].')
+          else
+            VERSION=$(echo ${{ github.ref.name }} | tr -cd '[:digit:].')
+          fi
+            echo "VERSION=${VERSION}" >> $GITHUB_ENV
+            echo ${VERSION}
+      - name: apt/rpm
+        run: |
+          ssh fileserver.clustercat.com "cd packages/nmcli; export VERSION=${{ ENV.version }}; export REVISION=0; ./buildall.sh "

+ 5 - 4
.github/workflows/publish-docker.yml

@@ -6,8 +6,11 @@ on:
       tag:
       tag:
         description: 'docker tag'
         description: 'docker tag'
         required: true
         required: true
-  release:
-    types: [published]
+  workflow_call:
+    inputs:
+      tag:
+        type: string
+        required: true
 
 
 jobs:
 jobs:
   docker:
   docker:
@@ -48,7 +51,6 @@ jobs:
           push: true
           push: true
           tags: ${{ github.repository }}:${{ env.TAG }}, ${{ github.repository }}:latest
           tags: ${{ github.repository }}:${{ env.TAG }}, ${{ github.repository }}:latest
           build-args: | 
           build-args: | 
-            version=${{ env.TAG }}
             tags=ce
             tags=ce
 
 
   docker-ee:
   docker-ee:
@@ -89,5 +91,4 @@ jobs:
           push: true
           push: true
           tags: ${{ github.repository }}:${{ env.TAG }}-ee
           tags: ${{ github.repository }}:${{ env.TAG }}-ee
           build-args: |
           build-args: |
-            version=${{ env.TAG }}
             tags=ee
             tags=ee

+ 32 - 0
.github/workflows/pull-request.yml

@@ -0,0 +1,32 @@
+# creates a PR from release branch to master
+name: Create Release PR to master
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+  release:
+    types: [released]
+
+jobs:
+  pr-to-main:
+    runs-on: ubuntu-latest
+    steps:
+      - name: create pr
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          gh api --method POST \
+          -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28'  \
+          /repos/${{ github.repository }}/pulls \
+          -f title='${{ github.event.inputs.version }}' \
+          -f head='release_${{ github.event.inputs.version }}' \
+          -f base="master"
+
+

+ 0 - 29
.github/workflows/purgeGHCR.yml

@@ -1,29 +0,0 @@
-name: Purge untagged images from GHCR
-
-on: 
-    workflow_dispatch:
-    schedule:
-      - cron: '1 1 1 * *'
-jobs:
-  purge:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Prune Netmaker
-        uses: vlaurin/action-ghcr-prune@main
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          organization: gravitl
-          container: netmaker
-          dry-run: false # Dry-run first, then change to `false`
-          untagged: true
-      - name: Prune Netclient
-        uses: vlaurin/action-ghcr-prune@main
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          organization: gravitl
-          container: netclient
-          dry-run: false # Dry-run first, then change to `false`
-          untagged: true
-          

+ 50 - 0
.github/workflows/release-assets.yml

@@ -0,0 +1,50 @@
+# generates release assets and uploads to release 
+name: Upload Release Assets
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+      prerelease:
+        required: true
+        type: boolean
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+      prerelease:
+        required: true
+        type: boolean
+        description: "prerelease"
+jobs:
+  release-assets:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          ref: release_${{ github.event.inputs.version}}
+          fetch-depth: 0
+      - name: Get Tags
+        run: |
+          git fetch --force --tags
+      - name: Setup go
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.19
+      - name: GoReleaser (full release)
+        if: ${{ github.event.inputs.prerelease == 'false'}}
+        uses: goreleaser/goreleaser-action@v4
+        with:
+          args: release --clean --release-notes release.md
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      - name: GoReleaser (prerelease)
+        if: ${{ github.event.inputs.prerelease == 'false'}}
+        uses: goreleaser/goreleaser-action@v4
+        with:
+          args: release --clean --release-notes release.md -f .goreleaser.prerelease.yaml
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 39 - 0
.github/workflows/release-branch.yml

@@ -0,0 +1,39 @@
+# creates new branch (release_{{ version }} with tag {{ version }} from develop 
+# will fail if branch or tag already exists on github
+name: Create Release Branch
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+jobs:
+  release-branch:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout develop
+        uses: actions/checkout@v3
+        with:
+          ref: develop
+      - name: setup go
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.19
+      - name: setup git
+        run: |
+          git config user.name "Github Actions"
+          git config user.emaail "[email protected]"
+      - name: create release Branch
+        run: |
+          git switch -c release_${{ github.event.inputs.version }}
+          git tag -f ${{ github.event.inputs.version }}
+          #push branch
+          git push origin release_${{ github.event.inputs.version }}
+          #push tag
+          git push origin ${{ github.event.inputs.version }}
+  

+ 49 - 0
.github/workflows/release.yml

@@ -0,0 +1,49 @@
+# creates a release from develop
+# creates release branch, generates release assets, publishes docker image and copies release.md to release
+# if formal release, linux packages are generated and a PR from release branch to master is created 
+name: Release
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "new version number"
+        required: true
+      prerelease:
+        required: true
+        type: boolean
+        description: Is this a pre-release
+
+jobs:
+
+  release-branch:
+    uses: ./.github/workflows/release-branch.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+  
+  release-assets:
+    needs: release-branch
+    uses: ./.github/workflows/release-assets.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+      prerelease: ${{ github.event.inputs.prerelease == 'true' }}
+
+  docker:
+    needs: release-branch
+    uses: ./.github/workflows/publish-docker.yml
+    with:
+      tag: ${{ github.event.inputs.version }}
+
+  packages:
+    if: ${{ github.event.inputs.prerelease == 'false' }}
+    needs: release-branch
+    uses: ./.github/workflows/packages.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+
+  pull-request:
+    if: ${{ github.event.inputs.prerelease == 'false' }}
+    needs: release-branch
+    uses: ./.github/workflows/pull-request.yml
+    with:
+      version: ${{ github.event.inputs.version }}

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

@@ -49,6 +49,7 @@ jobs:
           go-version: 1.19
           go-version: 1.19
       - name: run tests
       - name: run tests
         run: |
         run: |
+          go vet ./...
           go test -p 1 ./... -v
           go test -p 1 ./... -v
         env:
         env:
           DATABASE: sqlite
           DATABASE: sqlite

+ 41 - 0
.github/workflows/upgraderelease.yml

@@ -0,0 +1,41 @@
+name: UpgradeRelease
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "release/version to update"
+        required: true
+
+jobs:
+
+  update:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@v3
+        with:
+          ref: release_${{ github.event.inputs.version }}
+          fetch-depth: 0
+      - run: |
+          git fetch --force --tags
+      - name: Setup go
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.19
+      - name: goreleaser
+        uses: goreleaser/goreleaser-action@v4
+        with:
+          args: release --clean --release-notes release.md -f .goreleaser.update.yaml
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN  }}
+
+  packages:
+    uses: ./.github/workflows/packages.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+
+  pull-request:
+    uses: ./.github/workflows/pull-request.yml
+    with:
+      version: ${{ github.event.inputs.version }}

+ 1 - 0
.gitignore

@@ -24,3 +24,4 @@ data/
 .idea/
 .idea/
 netmaker.exe
 netmaker.exe
 netmaker.code-workspace
 netmaker.code-workspace
+dist/

+ 34 - 0
.goreleaser.prerelease.yaml

@@ -0,0 +1,34 @@
+before:
+  hooks:
+    # You may remove this if you don't use go modules.
+    - go mod tidy
+builds:
+  - main: ./
+    env:
+      - CGO_ENABLED=1
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+    binary: '{{ .ProjectName }}'
+
+  - main: ./cli
+    id: 'nmctl'
+    env:
+      - CGO_ENABLED=0
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+      - linux_arm64
+      - darwin_amd64
+      - darwin_arm64
+      - freebsd_amd64
+      - windows_amd64
+    binary: 'nmctl'
+archives:
+  - format: binary
+    name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}'
+release:
+  prerelease: true
+    

+ 4 - 0
.goreleaser.update.yaml

@@ -0,0 +1,4 @@
+builds:
+  - skip: true
+release:
+  prerelease: false

+ 34 - 0
.goreleaser.yaml

@@ -0,0 +1,34 @@
+before:
+  hooks:
+    # You may remove this if you don't use go modules.
+    - go mod tidy
+builds:
+  - main: ./
+    env:
+      - CGO_ENABLED=1
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+    binary: '{{ .ProjectName }}'
+
+  - main: ./cli
+    id: 'nmctl'
+    env:
+      - CGO_ENABLED=0
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+      - linux_arm64
+      - darwin_amd64
+      - darwin_arm64
+      - freebsd_amd64
+      - windows_amd64
+    binary: 'nmctl'
+archives:
+  - format: binary
+    name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}'
+release:
+  prerelease: false
+    

+ 1 - 6
Dockerfile

@@ -1,18 +1,13 @@
 #first stage - builder
 #first stage - builder
 FROM gravitl/go-builder as builder
 FROM gravitl/go-builder as builder
-ARG version
 ARG tags 
 ARG tags 
 WORKDIR /app
 WORKDIR /app
 COPY . .
 COPY . .
-ENV GO111MODULE=auto
 
 
-RUN apk add git
-RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -X 'main.version=${version}'" -tags ${tags} .
+RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w" -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
 # RUN go build -tags=ee . -o netmaker main.go
 FROM alpine:3.16.2
 FROM alpine:3.16.2
 
 
-# add a c lib
-RUN apk add gcompat iptables wireguard-tools
 # set the working directory
 # set the working directory
 WORKDIR /root/
 WORKDIR /root/
 RUN mkdir -p /etc/netclient/config
 RUN mkdir -p /etc/netclient/config

+ 2 - 2
README.md

@@ -17,7 +17,7 @@
 
 
 <p align="center">
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.18.0-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.18.2-informational?style=flat-square" />
   </a>
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
@@ -57,7 +57,7 @@
 3. (optional) Prepare DNS - Set a wildcard subdomain in your DNS for Netmaker, e.g. *.netmaker.example.com
 3. (optional) Prepare DNS - Set a wildcard subdomain in your DNS for Netmaker, e.g. *.netmaker.example.com
 4. Run the script: 
 4. Run the script: 
 
 
-`sudo wget -qO /root/nm-quick-interactive.sh https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick-interactive.sh && sudo chmod +x /root/nm-quick-interactive.sh && sudo /root/nm-quick-interactive.sh`  
+`sudo wget -qO /root/nm-quick.sh https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh && sudo chmod +x /root/nm-quick.sh && sudo /root/nm-quick.sh`  
 
 
 This script gives you the option to deploy the Community or Enterprise version of Netmaker. If deploying Enterprise, you get a free account with a 50 node limit by default. It also gives you the option to use your own domain (recommended) or an auto-generated domain. 
 This script gives you the option to deploy the Community or Enterprise version of Netmaker. If deploying Enterprise, you get a free account with a 50 node limit by default. It also gives you the option to use your own domain (recommended) or an auto-generated domain. 
 
 

+ 20 - 0
cli/cmd/host/add_network.go

@@ -0,0 +1,20 @@
+package host
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var addHostNetworkCmd = &cobra.Command{
+	Use:   "add_network HostID Network",
+	Args:  cobra.ExactArgs(2),
+	Short: "Add a network to a host",
+	Long:  `Add a network to a host`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.AddHostToNetwork(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(addHostNetworkCmd)
+}

+ 20 - 0
cli/cmd/host/delete_network.go

@@ -0,0 +1,20 @@
+package host
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var deleteHostNetworkCmd = &cobra.Command{
+	Use:   "delete_network HostID Network",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a network from a host",
+	Long:  `Delete a network from a host`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteHostFromNetwork(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(deleteHostNetworkCmd)
+}

+ 0 - 22
cli/cmd/host/update_networks.go

@@ -1,22 +0,0 @@
-package host
-
-import (
-	"strings"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var hostUpdateNetworksCmd = &cobra.Command{
-	Use:   "update_network HostID Networks(comma separated list)",
-	Args:  cobra.ExactArgs(2),
-	Short: "Update a host's networks",
-	Long:  `Update a host's networks`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.UpdateHostNetworks(args[0], strings.Split(args[1], ",")))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(hostUpdateNetworksCmd)
-}

+ 0 - 4
cli/cmd/network/create.go

@@ -35,9 +35,6 @@ var networkCreateCmd = &cobra.Command{
 			if udpHolePunch {
 			if udpHolePunch {
 				network.DefaultUDPHolePunch = "yes"
 				network.DefaultUDPHolePunch = "yes"
 			}
 			}
-			if localNetwork {
-				network.IsLocal = "yes"
-			}
 			if defaultACL {
 			if defaultACL {
 				network.DefaultACL = "yes"
 				network.DefaultACL = "yes"
 			}
 			}
@@ -62,7 +59,6 @@ func init() {
 	networkCreateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
 	networkCreateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
 	networkCreateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
 	networkCreateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
 	networkCreateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
 	networkCreateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
-	networkCreateCmd.Flags().BoolVar(&localNetwork, "local", false, "Is the network local (LAN) ?")
 	networkCreateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
 	networkCreateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
 	networkCreateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
 	networkCreateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
 	networkCreateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")
 	networkCreateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")

+ 0 - 1
cli/cmd/network/flags.go

@@ -6,7 +6,6 @@ var (
 	address                   string
 	address                   string
 	address6                  string
 	address6                  string
 	udpHolePunch              bool
 	udpHolePunch              bool
-	localNetwork              bool
 	defaultACL                bool
 	defaultACL                bool
 	defaultInterface          string
 	defaultInterface          string
 	defaultListenPort         int
 	defaultListenPort         int

+ 0 - 4
cli/cmd/network/update.go

@@ -38,9 +38,6 @@ var networkUpdateCmd = &cobra.Command{
 			if udpHolePunch {
 			if udpHolePunch {
 				network.DefaultUDPHolePunch = "yes"
 				network.DefaultUDPHolePunch = "yes"
 			}
 			}
-			if localNetwork {
-				network.IsLocal = "yes"
-			}
 			if defaultACL {
 			if defaultACL {
 				network.DefaultACL = "yes"
 				network.DefaultACL = "yes"
 			}
 			}
@@ -63,7 +60,6 @@ func init() {
 	networkUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
 	networkUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
 	networkUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
 	networkUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
 	networkUpdateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
 	networkUpdateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
-	networkUpdateCmd.Flags().BoolVar(&localNetwork, "local", false, "Is the network local (LAN) ?")
 	networkUpdateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
 	networkUpdateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
 	networkUpdateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
 	networkUpdateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
 	networkUpdateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")
 	networkUpdateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")

+ 8 - 5
cli/functions/host.go

@@ -26,11 +26,14 @@ func UpdateHost(hostID string, body *models.ApiHost) *models.ApiHost {
 	return request[models.ApiHost](http.MethodPut, "/api/hosts/"+hostID, body)
 	return request[models.ApiHost](http.MethodPut, "/api/hosts/"+hostID, body)
 }
 }
 
 
-// UpdateHostNetworks - update a host's networks
-func UpdateHostNetworks(hostID string, networks []string) *hostNetworksUpdatePayload {
-	return request[hostNetworksUpdatePayload](http.MethodPut, "/api/hosts/"+hostID+"/networks", &hostNetworksUpdatePayload{
-		Networks: networks,
-	})
+// AddHostToNetwork - add a network to host
+func AddHostToNetwork(hostID, network string) *hostNetworksUpdatePayload {
+	return request[hostNetworksUpdatePayload](http.MethodPost, "/api/hosts/"+hostID+"/networks/"+network, nil)
+}
+
+// DeleteHostFromNetwork - deletes a network from host
+func DeleteHostFromNetwork(hostID, network string) *hostNetworksUpdatePayload {
+	return request[hostNetworksUpdatePayload](http.MethodDelete, "/api/hosts/"+hostID+"/networks/"+network, nil)
 }
 }
 
 
 // CreateRelay - turn a host into a relay
 // CreateRelay - turn a host into a relay

+ 0 - 2
cli/samples/network.json

@@ -10,11 +10,9 @@
     "defaultinterface": "nm-test3",
     "defaultinterface": "nm-test3",
     "accesskeys": [],
     "accesskeys": [],
     "allowmanualsignup": "no",
     "allowmanualsignup": "no",
-    "islocal": "no",
     "isipv4": "yes",
     "isipv4": "yes",
     "isipv6": "no",
     "isipv6": "no",
     "ispointtosite": "no",
     "ispointtosite": "no",
-    "localrange": "",
     "defaultudpholepunch": "yes",
     "defaultudpholepunch": "yes",
     "defaultextclientdns": "",
     "defaultextclientdns": "",
     "defaultmtu": 1280,
     "defaultmtu": 1280,

+ 0 - 4
cli/samples/node.json

@@ -18,11 +18,9 @@
       "defaultkeepalive": 20,
       "defaultkeepalive": 20,
       "accesskeys": [],
       "accesskeys": [],
       "allowmanualsignup": "no",
       "allowmanualsignup": "no",
-      "islocal": "no",
       "isipv4": "no",
       "isipv4": "no",
       "isipv6": "yes",
       "isipv6": "yes",
       "ispointtosite": "no",
       "ispointtosite": "no",
-      "localrange": "",
       "defaultudpholepunch": "yes",
       "defaultudpholepunch": "yes",
       "defaultextclientdns": "",
       "defaultextclientdns": "",
       "defaultmtu": 1280,
       "defaultmtu": 1280,
@@ -82,8 +80,6 @@
     "dnson": "no",
     "dnson": "no",
     "isserver": "yes",
     "isserver": "yes",
     "action": "noop",
     "action": "noop",
-    "islocal": "no",
-    "localrange": "",
     "ipforwarding": "yes",
     "ipforwarding": "yes",
     "os": "linux",
     "os": "linux",
     "mtu": 1280,
     "mtu": 1280,

+ 83 - 0
compose/docker-compose-emqx.yml

@@ -0,0 +1,83 @@
+version: "3.4"
+
+services:
+  netmaker:
+    container_name: netmaker
+    image: gravitl/netmaker:v0.18.2
+    restart: always
+    volumes:
+      - dnsconfig:/root/config/dnsconfig
+      - sqldata:/root/data
+    environment:
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN/mqtt"
+      BROKER_TYPE: "emqx"
+      EMQX_REST_ENDPOINT: "http://mq:18083"
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN"
+      STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
+      SERVER_HOST: "SERVER_PUBLIC_IP"
+      SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
+      COREDNS_ADDR: "SERVER_PUBLIC_IP"
+      DNS_MODE: "on"
+      SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
+      API_PORT: "8081"
+      MASTER_KEY: "REPLACE_MASTER_KEY"
+      CORS_ALLOWED_ORIGIN: "*"
+      DISPLAY_KEYS: "on"
+      DATABASE: "sqlite"
+      NODE_ID: "netmaker-server-1"
+      SERVER_BROKER_ENDPOINT: "ws://mq:8083/mqtt"
+      STUN_PORT: "3478"      
+      VERBOSITY: "1"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
+    ports:
+      - "3478:3478/udp"
+  netmaker-ui:
+    container_name: netmaker-ui
+    image: gravitl/netmaker-ui:v0.18.2
+    depends_on:
+      - netmaker
+    links:
+      - "netmaker:api"
+    restart: always
+    environment:
+      BACKEND_URL: "https://api.NETMAKER_BASE_DOMAIN"
+  caddy:
+    image: caddy:2.6.2
+    container_name: caddy
+    restart: unless-stopped
+    volumes:
+      - /root/Caddyfile:/etc/caddy/Caddyfile
+      - caddy_data:/data
+      - caddy_conf:/config
+    ports:
+      - "80:80"
+      - "443:443"
+  coredns:
+    container_name: coredns
+    image: coredns/coredns
+    command: -conf /root/dnsconfig/Corefile
+    depends_on:
+      - netmaker
+    restart: always
+    volumes:
+      - dnsconfig:/root/dnsconfig
+  mq:
+    container_name: mq
+    image: emqx/emqx:5.0.17
+    restart: unless-stopped
+    environment:
+      EMQX_NAME: "emqx"
+      EMQX_DASHBOARD__DEFAULT_PASSWORD: "REPLACE_MQ_PASSWORD"
+      EMQX_DASHBOARD__DEFAULT_USERNAME: "REPLACE_MQ_USERNAME"
+    ports:
+      - "1883:1883" # MQTT
+      - "8883:8883" # SSL MQTT
+      - "8083:8083" # Websockets
+      - "18083:18083" # Dashboard/REST_API
+volumes:
+  caddy_data: {}
+  caddy_conf: {}
+  sqldata: {}
+  dnsconfig: {}
+  mosquitto_logs: {}

+ 19 - 24
compose/docker-compose.ee.yml

@@ -3,13 +3,16 @@ version: "3.4"
 services:
 services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.0-ee
+    image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     restart: always
     restart: always
     volumes:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
     environment:
     environment:
-      SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN/mqtt"
+      BROKER_TYPE: "emqx"
+      EMQX_REST_ENDPOINT: "http://mq:18083"
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN"
       STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
       STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
@@ -22,22 +25,19 @@ services:
       DISPLAY_KEYS: "on"
       DISPLAY_KEYS: "on"
       DATABASE: "sqlite"
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
       NODE_ID: "netmaker-server-1"
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
+      SERVER_BROKER_ENDPOINT: "ws://mq:8083/mqtt"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       STUN_PORT: "3478"
       STUN_PORT: "3478"
-      MQ_SERVER_PORT: "1883"
       VERBOSITY: "1"
       VERBOSITY: "1"
       METRICS_EXPORTER: "on"
       METRICS_EXPORTER: "on"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
-      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
-      MQ_USERNAME: "REPLACE_MQ_USERNAME"
     ports:
     ports:
-      - "51821-51830:51821-51830/udp"
       - "3478:3478/udp"
       - "3478:3478/udp"
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.0
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     links:
     links:
@@ -67,21 +67,17 @@ services:
       - dnsconfig:/root/dnsconfig
       - dnsconfig:/root/dnsconfig
   mq:
   mq:
     container_name: mq
     container_name: mq
-    image: eclipse-mosquitto:2.0.15-openssl
-    depends_on:
-      - netmaker
+    image: emqx/emqx:5.0.17
     restart: unless-stopped
     restart: unless-stopped
-    command: ["/mosquitto/config/wait.sh"]
     environment:
     environment:
-      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
-      MQ_USERNAME: "REPLACE_MQ_USERNAME"
-    volumes:
-      - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
-      - /root/wait.sh:/mosquitto/config/wait.sh
-      - mosquitto_logs:/mosquitto/log
+      EMQX_NAME: "emqx"
+      EMQX_DASHBOARD__DEFAULT_PASSWORD: "REPLACE_MQ_PASSWORD"
+      EMQX_DASHBOARD__DEFAULT_USERNAME: "REPLACE_MQ_USERNAME"
     ports:
     ports:
-      - "1883:1883"
-      - "8883:8883"
+      - "1883:1883" # MQTT
+      - "8883:8883" # SSL MQTT
+      - "8083:8083" # Websockets
+      - "18083:18083" # Dashboard/REST_API
   prometheus:
   prometheus:
     container_name: prometheus
     container_name: prometheus
     image: gravitl/netmaker-prometheus:latest
     image: gravitl/netmaker-prometheus:latest
@@ -115,9 +111,8 @@ services:
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     environment:
     environment:
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
-      MQ_SERVER_PORT: "1883"
+      SERVER_BROKER_ENDPOINT: "ws://mq:8083/mqtt"
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN/mqtt"
       PROMETHEUS: "on"
       PROMETHEUS: "on"
       VERBOSITY: "1"
       VERBOSITY: "1"
       API_PORT: "8085"
       API_PORT: "8085"

+ 16 - 12
compose/docker-compose.reference.yml

@@ -3,14 +3,15 @@ version: "3.4"
 services:
 services:
   netmaker: # The Primary Server for running Netmaker
   netmaker: # The Primary Server for running Netmaker
     container_name: netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.0
+    image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     restart: always
     restart: always
     volumes: # Volume mounts necessary for sql, coredns, and mqtt
     volumes: # Volume mounts necessary for sql, coredns, and mqtt
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
       - shared_certs:/etc/netmaker
       - shared_certs:/etc/netmaker
     environment: # Necessary capabilities to set iptables when running in container
     environment: # Necessary capabilities to set iptables when running in container
-      SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN" # The domain/host IP indicating the mq broker address
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN" # The domain/host IP indicating the mq broker address
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN" # The base domain of netmaker
       SERVER_HOST: "SERVER_PUBLIC_IP" # Set to public IP of machine.
       SERVER_HOST: "SERVER_PUBLIC_IP" # Set to public IP of machine.
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN" # Overrides SERVER_HOST if set. Useful for making HTTP available via different interfaces/networks.
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN" # Overrides SERVER_HOST if set. Useful for making HTTP available via different interfaces/networks.
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
@@ -25,9 +26,10 @@ services:
       DISPLAY_KEYS: "on" # Show keys permanently in UI (until deleted) as opposed to 1-time display.
       DISPLAY_KEYS: "on" # Show keys permanently in UI (until deleted) as opposed to 1-time display.
       DATABASE: "sqlite" # Database to use - sqlite, postgres, or rqlite
       DATABASE: "sqlite" # Database to use - sqlite, postgres, or rqlite
       NODE_ID: "netmaker-server-1" # used for HA - identifies this server vs other servers
       NODE_ID: "netmaker-server-1" # used for HA - identifies this server vs other servers
-      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.
-      MQ_SERVER_PORT: "1883" # the reachable port of MQ by the server - change if internal MQ port changes (or use external port if MQ is not on the same machine)
-      MQ_PORT: "443" # the reachable port of MQ - change if external MQ port changes (port on proxy, not necessarily the one exposed in docker-compose)
+      SERVER_BROKER_ENDPOINT: ""ws://mq:1883""  # 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.
+      MQ_USERNAME: "REPLACE_MQ_USERNAME" # the username to set for MQ access
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD" # the password to set for MQ access
+      STUN_PORT: "3478" # the reachable port of STUN on the server
       VERBOSITY: "1" # logging verbosity level - 1, 2, or 3
       VERBOSITY: "1" # logging verbosity level - 1, 2, or 3
       # this section is for OAuth
       # this section is for OAuth
       AUTH_PROVIDER: "" # "<azure-ad|github|google|oidc>"
       AUTH_PROVIDER: "" # "<azure-ad|github|google|oidc>"
@@ -37,10 +39,10 @@ services:
       AZURE_TENANT: "" # "<only for azure, you may optionally specify the tenant for the OAuth>"
       AZURE_TENANT: "" # "<only for azure, you may optionally specify the tenant for the OAuth>"
       OIDC_ISSUER: "" # https://oidc.yourprovider.com - URL of oidc provider
       OIDC_ISSUER: "" # https://oidc.yourprovider.com - URL of oidc provider
     ports:
     ports:
-      - "51821-51830:51821-51830/udp" # wireguard ports
+      - "3478:3478/udp" # the stun port
   netmaker-ui:  # The Netmaker UI Component
   netmaker-ui:  # The Netmaker UI Component
     container_name: netmaker-ui
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.0
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     links:
     links:
@@ -68,17 +70,20 @@ services:
     restart: always
     restart: always
     volumes:
     volumes:
       - dnsconfig:/root/dnsconfig
       - dnsconfig:/root/dnsconfig
-  mq: # the MQTT broker for netmaker
+  mq: # the mqtt broker for netmaker
     container_name: mq
     container_name: mq
     image: eclipse-mosquitto:2.0.15-openssl
     image: eclipse-mosquitto:2.0.15-openssl
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     restart: unless-stopped
     restart: unless-stopped
+    command: ["/mosquitto/config/wait.sh"]
+    environment:
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD" # must be same value as in netmaker env 
+      MQ_USERNAME: "REPLACE_MQ_USERNAME" # must be same value as in netmaker env
     volumes:
     volumes:
-      - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf # need to pull conf file from github before running (under docker/mosquitto.conf)
-      - mosquitto_data:/mosquitto/data
+      - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
+      - /root/wait.sh:/mosquitto/config/wait.sh
       - mosquitto_logs:/mosquitto/log
       - mosquitto_logs:/mosquitto/log
-      - shared_certs:/mosquitto/certs
     ports:
     ports:
       - "1883:1883"
       - "1883:1883"
       - "8883:8883"
       - "8883:8883"
@@ -88,5 +93,4 @@ volumes:
   shared_certs: {} # netmaker certs generated for MQ comms - used by nodes/servers
   shared_certs: {} # netmaker certs generated for MQ comms - used by nodes/servers
   sqldata: {} # storage for embedded sqlite
   sqldata: {} # storage for embedded sqlite
   dnsconfig: {} # storage for coredns
   dnsconfig: {} # storage for coredns
-  mosquitto_data: {} # storage for mqtt data
   mosquitto_logs: {} # storage for mqtt logs
   mosquitto_logs: {} # storage for mqtt logs

+ 5 - 9
compose/docker-compose.yml

@@ -3,13 +3,13 @@ version: "3.4"
 services:
 services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.0
+    image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     restart: always
     restart: always
     volumes:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
     environment:
     environment:
-      BROKER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN"
       SERVER_NAME: "NETMAKER_BASE_DOMAIN"
       SERVER_NAME: "NETMAKER_BASE_DOMAIN"
       STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
       STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_HOST: "SERVER_PUBLIC_IP"
@@ -23,20 +23,16 @@ services:
       DISPLAY_KEYS: "on"
       DISPLAY_KEYS: "on"
       DATABASE: "sqlite"
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
       NODE_ID: "netmaker-server-1"
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
-      MQ_SERVER_PORT: "1883"
+      SERVER_BROKER_ENDPOINT: "ws://mq:1883"
+      STUN_PORT: "3478"      
       VERBOSITY: "1"
       VERBOSITY: "1"
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
-      STUN_PORT: "3478"
-      PROXY: "on"
     ports:
     ports:
-      - "51821-51830:51821-51830/udp"
       - "3478:3478/udp"
       - "3478:3478/udp"
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.0
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     links:
     links:

+ 4 - 7
config/config.go

@@ -36,13 +36,15 @@ type ServerConfig struct {
 	APIConnString         string `yaml:"apiconn"`
 	APIConnString         string `yaml:"apiconn"`
 	APIHost               string `yaml:"apihost"`
 	APIHost               string `yaml:"apihost"`
 	APIPort               string `yaml:"apiport"`
 	APIPort               string `yaml:"apiport"`
-	MQHOST                string `yaml:"mqhost"`
+	Broker                string `yam:"broker"`
+	ServerBrokerEndpoint  string `yaml:"serverbrokerendpoint"`
+	BrokerType            string `yaml:"brokertype"`
+	EmqxRestEndpoint      string `yaml:"emqxrestendpoint"`
 	MasterKey             string `yaml:"masterkey"`
 	MasterKey             string `yaml:"masterkey"`
 	DNSKey                string `yaml:"dnskey"`
 	DNSKey                string `yaml:"dnskey"`
 	AllowedOrigin         string `yaml:"allowedorigin"`
 	AllowedOrigin         string `yaml:"allowedorigin"`
 	NodeID                string `yaml:"nodeid"`
 	NodeID                string `yaml:"nodeid"`
 	RestBackend           string `yaml:"restbackend"`
 	RestBackend           string `yaml:"restbackend"`
-	AgentBackend          string `yaml:"agentbackend"`
 	MessageQueueBackend   string `yaml:"messagequeuebackend"`
 	MessageQueueBackend   string `yaml:"messagequeuebackend"`
 	DNSMode               string `yaml:"dnsmode"`
 	DNSMode               string `yaml:"dnsmode"`
 	DisableRemoteIPCheck  string `yaml:"disableremoteipcheck"`
 	DisableRemoteIPCheck  string `yaml:"disableremoteipcheck"`
@@ -50,9 +52,7 @@ type ServerConfig struct {
 	SQLConn               string `yaml:"sqlconn"`
 	SQLConn               string `yaml:"sqlconn"`
 	Platform              string `yaml:"platform"`
 	Platform              string `yaml:"platform"`
 	Database              string `yaml:"database"`
 	Database              string `yaml:"database"`
-	DefaultNodeLimit      int32  `yaml:"defaultnodelimit"`
 	Verbosity             int32  `yaml:"verbosity"`
 	Verbosity             int32  `yaml:"verbosity"`
-	ServerCheckinInterval int64  `yaml:"servercheckininterval"`
 	AuthProvider          string `yaml:"authprovider"`
 	AuthProvider          string `yaml:"authprovider"`
 	OIDCIssuer            string `yaml:"oidcissuer"`
 	OIDCIssuer            string `yaml:"oidcissuer"`
 	ClientID              string `yaml:"clientid"`
 	ClientID              string `yaml:"clientid"`
@@ -62,10 +62,7 @@ type ServerConfig struct {
 	AzureTenant           string `yaml:"azuretenant"`
 	AzureTenant           string `yaml:"azuretenant"`
 	Telemetry             string `yaml:"telemetry"`
 	Telemetry             string `yaml:"telemetry"`
 	HostNetwork           string `yaml:"hostnetwork"`
 	HostNetwork           string `yaml:"hostnetwork"`
-	MQPort                string `yaml:"mqport"`
-	MQServerPort          string `yaml:"mqserverport"`
 	Server                string `yaml:"server"`
 	Server                string `yaml:"server"`
-	Broker                string `yam:"broker"`
 	PublicIPService       string `yaml:"publicipservice"`
 	PublicIPService       string `yaml:"publicipservice"`
 	MQPassword            string `yaml:"mqpassword"`
 	MQPassword            string `yaml:"mqpassword"`
 	MQUserName            string `yaml:"mqusername"`
 	MQUserName            string `yaml:"mqusername"`

+ 4 - 4
config/config_test.go

@@ -1,7 +1,7 @@
-//Environment file for getting variables
-//Currently the only thing it does is set the master password
-//Should probably have it take over functions from OS such as port and mongodb connection details
-//Reads from the config/environments/dev.yaml file by default
+// Environment file for getting variables
+// Currently the only thing it does is set the master password
+// Should probably have it take over functions from OS such as port and mongodb connection details
+// Reads from the config/environments/dev.yaml file by default
 package config
 package config
 
 
 import (
 import (

+ 0 - 1
config/environments/dev.yaml

@@ -4,7 +4,6 @@ server:
   masterkey: "" # defaults to 'secretkey' or MASTER_KEY (if set)
   masterkey: "" # defaults to 'secretkey' or MASTER_KEY (if set)
   allowedorigin: "" # defaults to '*' or CORS_ALLOWED_ORIGIN (if set)
   allowedorigin: "" # defaults to '*' or CORS_ALLOWED_ORIGIN (if set)
   restbackend: "" # defaults to "on" or REST_BACKEND (if set)
   restbackend: "" # defaults to "on" or REST_BACKEND (if set)
-  agentbackend: "" # defaults to "on" or AGENT_BACKEND (if set)
   dnsmode: "" # defaults to "on" or DNS_MODE (if set)
   dnsmode: "" # defaults to "on" or DNS_MODE (if set)
   sqlconn: "" # defaults to "http://" or SQL_CONN (if set)
   sqlconn: "" # defaults to "http://" or SQL_CONN (if set)
   disableremoteipcheck: "" # defaults to "false" or DISABLE_REMOTE_IP_CHECK (if set)
   disableremoteipcheck: "" # defaults to "false" or DISABLE_REMOTE_IP_CHECK (if set)

+ 1 - 2
controllers/config/environments/dev.yaml

@@ -4,7 +4,6 @@ server:
   masterkey: ""
   masterkey: ""
   allowedorigin: "*"
   allowedorigin: "*"
   restbackend: true            
   restbackend: true            
-  agentbackend: true
   defaultnetname: "default"
   defaultnetname: "default"
   defaultnetrange: "10.10.10.0/24"
   defaultnetrange: "10.10.10.0/24"
-  createdefault: true
+  createdefault: true

+ 5 - 11
controllers/controller.go

@@ -4,11 +4,8 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
-	"os"
-	"os/signal"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
-	"syscall"
 	"time"
 	"time"
 
 
 	"github.com/gorilla/handlers"
 	"github.com/gorilla/handlers"
@@ -29,10 +26,11 @@ var HttpHandlers = []interface{}{
 	ipHandlers,
 	ipHandlers,
 	loggerHandlers,
 	loggerHandlers,
 	hostHandlers,
 	hostHandlers,
+	enrollmentKeyHandlers,
 }
 }
 
 
 // HandleRESTRequests - handles the rest requests
 // HandleRESTRequests - handles the rest requests
-func HandleRESTRequests(wg *sync.WaitGroup) {
+func HandleRESTRequests(wg *sync.WaitGroup, ctx context.Context) {
 	defer wg.Done()
 	defer wg.Done()
 
 
 	r := mux.NewRouter()
 	r := mux.NewRouter()
@@ -58,18 +56,14 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
 	}()
 	}()
 	logger.Log(0, "REST Server successfully started on port ", port, " (REST)")
 	logger.Log(0, "REST Server successfully started on port ", port, " (REST)")
 
 
-	// Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)
-	// Ignore other incoming signals
-	ctx, stop := signal.NotifyContext(context.TODO(), syscall.SIGTERM, os.Interrupt)
-	defer stop()
-
 	// Block main routine until a signal is received
 	// Block main routine until a signal is received
 	// As long as user doesn't press CTRL+C a message is not passed and our main routine keeps running
 	// As long as user doesn't press CTRL+C a message is not passed and our main routine keeps running
 	<-ctx.Done()
 	<-ctx.Done()
-
 	// After receiving CTRL+C Properly stop the server
 	// After receiving CTRL+C Properly stop the server
 	logger.Log(0, "Stopping the REST server...")
 	logger.Log(0, "Stopping the REST server...")
+	if err := srv.Shutdown(context.TODO()); err != nil {
+		logger.Log(0, "REST shutdown error occurred -", err.Error())
+	}
 	logger.Log(0, "REST Server closed.")
 	logger.Log(0, "REST Server closed.")
 	logger.DumpFile(fmt.Sprintf("data/netmaker.log.%s", time.Now().Format(logger.TimeFormatDay)))
 	logger.DumpFile(fmt.Sprintf("data/netmaker.log.%s", time.Now().Format(logger.TimeFormatDay)))
-	srv.Shutdown(context.TODO())
 }
 }

+ 18 - 3
controllers/dns.go

@@ -176,9 +176,14 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	logger.Log(1, "new DNS record added:", entry.Name)
 	logger.Log(1, "new DNS record added:", entry.Name)
 	if servercfg.IsMessageQueueBackend() {
 	if servercfg.IsMessageQueueBackend() {
-		if err = mq.PublishPeerUpdate(); err != nil {
-			logger.Log(0, "failed to publish peer update after ACL update on", entry.Network)
-		}
+		go func() {
+			if err = mq.PublishPeerUpdate(); err != nil {
+				logger.Log(0, "failed to publish peer update after ACL update on", entry.Network)
+			}
+			if err := mq.PublishCustomDNS(&entry); err != nil {
+				logger.Log(0, "error publishing custom dns", err.Error())
+			}
+		}()
 	}
 	}
 	logger.Log(2, r.Header.Get("user"),
 	logger.Log(2, r.Header.Get("user"),
 		fmt.Sprintf("DNS entry is set: %+v", entry))
 		fmt.Sprintf("DNS entry is set: %+v", entry))
@@ -221,6 +226,16 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	json.NewEncoder(w).Encode(entrytext + " deleted.")
 	json.NewEncoder(w).Encode(entrytext + " deleted.")
+	go func() {
+		dns := models.DNSUpdate{
+			Action: models.DNSDeleteByName,
+			Name:   entrytext,
+		}
+		if err := mq.PublishDNSUpdate(params["network"], dns); err != nil {
+			logger.Log(0, "failed to publish dns update", err.Error())
+		}
+	}()
+
 }
 }
 
 
 // GetDNSEntry - gets a DNS entry
 // GetDNSEntry - gets a DNS entry

+ 29 - 40
controllers/dns_test.go

@@ -6,17 +6,16 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
-	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
 )
 )
 
 
 var dnsHost models.Host
 var dnsHost models.Host
 
 
 func TestGetAllDNS(t *testing.T) {
 func TestGetAllDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -28,7 +27,7 @@ func TestGetAllDNS(t *testing.T) {
 	})
 	})
 	t.Run("OneEntry", func(t *testing.T) {
 	t.Run("OneEntry", func(t *testing.T) {
 		entry := models.DNSEntry{
 		entry := models.DNSEntry{
-			"10.0.0.3", "", "newhost", "skynet",
+			Address: "10.0.0.3", Name: "newhost", Network: "skynet",
 		}
 		}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
@@ -37,7 +36,7 @@ func TestGetAllDNS(t *testing.T) {
 		assert.Equal(t, 1, len(entries))
 		assert.Equal(t, 1, len(entries))
 	})
 	})
 	t.Run("MultipleEntry", func(t *testing.T) {
 	t.Run("MultipleEntry", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.7", "", "anotherhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.7", Name: "anotherhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		entries, err := logic.GetAllDNS()
 		entries, err := logic.GetAllDNS()
@@ -47,7 +46,6 @@ func TestGetAllDNS(t *testing.T) {
 }
 }
 
 
 func TestGetNodeDNS(t *testing.T) {
 func TestGetNodeDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -94,7 +92,6 @@ func TestGetNodeDNS(t *testing.T) {
 	})
 	})
 }
 }
 func TestGetCustomDNS(t *testing.T) {
 func TestGetCustomDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -115,7 +112,7 @@ func TestGetCustomDNS(t *testing.T) {
 		assert.Equal(t, 0, len(dns))
 		assert.Equal(t, 0, len(dns))
 	})
 	})
 	t.Run("EntryExist", func(t *testing.T) {
 	t.Run("EntryExist", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "custom1", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.3", Name: "custom1", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetCustomDNS("skynet")
 		dns, err := logic.GetCustomDNS("skynet")
@@ -123,7 +120,7 @@ func TestGetCustomDNS(t *testing.T) {
 		assert.Equal(t, 1, len(dns))
 		assert.Equal(t, 1, len(dns))
 	})
 	})
 	t.Run("MultipleEntries", func(t *testing.T) {
 	t.Run("MultipleEntries", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.4", "", "host4", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.4", Name: "host4", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetCustomDNS("skynet")
 		dns, err := logic.GetCustomDNS("skynet")
@@ -133,7 +130,6 @@ func TestGetCustomDNS(t *testing.T) {
 }
 }
 
 
 func TestGetDNSEntryNum(t *testing.T) {
 func TestGetDNSEntryNum(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -143,7 +139,7 @@ func TestGetDNSEntryNum(t *testing.T) {
 		assert.Equal(t, 0, num)
 		assert.Equal(t, 0, num)
 	})
 	})
 	t.Run("NodeExists", func(t *testing.T) {
 	t.Run("NodeExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		num, err := logic.GetDNSEntryNum("newhost", "skynet")
 		num, err := logic.GetDNSEntryNum("newhost", "skynet")
@@ -152,7 +148,6 @@ func TestGetDNSEntryNum(t *testing.T) {
 	})
 	})
 }
 }
 func TestGetDNS(t *testing.T) {
 func TestGetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -162,7 +157,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Nil(t, dns)
 		assert.Nil(t, dns)
 	})
 	})
 	t.Run("CustomDNSExists", func(t *testing.T) {
 	t.Run("CustomDNSExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetDNS("skynet")
 		dns, err := logic.GetDNS("skynet")
@@ -182,7 +177,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Equal(t, 1, len(dns))
 		assert.Equal(t, 1, len(dns))
 	})
 	})
 	t.Run("NodeAndCustomDNS", func(t *testing.T) {
 	t.Run("NodeAndCustomDNS", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		dns, err := logic.GetDNS("skynet")
 		dns, err := logic.GetDNS("skynet")
@@ -196,18 +191,16 @@ func TestGetDNS(t *testing.T) {
 }
 }
 
 
 func TestCreateDNS(t *testing.T) {
 func TestCreateDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	dns, err := logic.CreateDNS(entry)
 	dns, err := logic.CreateDNS(entry)
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 	assert.Equal(t, "newhost", dns.Name)
 	assert.Equal(t, "newhost", dns.Name)
 }
 }
 
 
 func TestSetDNS(t *testing.T) {
 func TestSetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -239,7 +232,7 @@ func TestSetDNS(t *testing.T) {
 		assert.Contains(t, string(content), "linuxhost.skynet")
 		assert.Contains(t, string(content), "linuxhost.skynet")
 	})
 	})
 	t.Run("EntryExists", func(t *testing.T) {
 	t.Run("EntryExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.3", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		err = logic.SetDNS()
 		err = logic.SetDNS()
@@ -255,12 +248,11 @@ func TestSetDNS(t *testing.T) {
 }
 }
 
 
 func TestGetDNSEntry(t *testing.T) {
 func TestGetDNSEntry(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	createTestNode()
 	createTestNode()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	_, _ = logic.CreateDNS(entry)
 	_, _ = logic.CreateDNS(entry)
 	t.Run("wrong net", func(t *testing.T) {
 	t.Run("wrong net", func(t *testing.T) {
 		entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat")
 		entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat")
@@ -285,11 +277,10 @@ func TestGetDNSEntry(t *testing.T) {
 }
 }
 
 
 func TestDeleteDNS(t *testing.T) {
 func TestDeleteDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	_, _ = logic.CreateDNS(entry)
 	_, _ = logic.CreateDNS(entry)
 	t.Run("EntryExists", func(t *testing.T) {
 	t.Run("EntryExists", func(t *testing.T) {
 		err := logic.DeleteDNS("newhost", "skynet")
 		err := logic.DeleteDNS("newhost", "skynet")
@@ -307,20 +298,19 @@ func TestDeleteDNS(t *testing.T) {
 }
 }
 
 
 func TestValidateDNSUpdate(t *testing.T) {
 func TestValidateDNSUpdate(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "skynet"}
 	t.Run("BadNetwork", func(t *testing.T) {
 	t.Run("BadNetwork", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "badnet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 	})
 	})
 	t.Run("EmptyNetwork", func(t *testing.T) {
 	t.Run("EmptyNetwork", func(t *testing.T) {
-		//this can't actually happen as change.Network is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", ""}
+		// this can't actually happen as change.Network is populated if is blank
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
@@ -333,14 +323,14 @@ func TestValidateDNSUpdate(t *testing.T) {
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// })
 	// })
 	t.Run("BadAddress", func(t *testing.T) {
 	t.Run("BadAddress", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
+		change := models.DNSEntry{Address: "10.0.256.1", Name: "myhost", Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	})
 	t.Run("EmptyName", func(t *testing.T) {
 	t.Run("EmptyName", func(t *testing.T) {
-		//this can't actually happen as change.Name is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
+		// this can't actually happen as change.Name is populated if is blank
+		change := models.DNSEntry{Address: "10.0.0.2", Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -350,29 +340,28 @@ func TestValidateDNSUpdate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 			name = name + "a"
 		}
 		}
-		change := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: name, Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	})
 	t.Run("NameUnique", func(t *testing.T) {
 	t.Run("NameUnique", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", "wirecat"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "wirecat"}
 		_, _ = logic.CreateDNS(entry)
 		_, _ = logic.CreateDNS(entry)
 		_, _ = logic.CreateDNS(change)
 		_, _ = logic.CreateDNS(change)
 		err := logic.ValidateDNSUpdate(change, entry)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag")
-		//cleanup
+		// cleanup
 		err = logic.DeleteDNS("myhost", "wirecat")
 		err = logic.DeleteDNS("myhost", "wirecat")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
 
 
 }
 }
 func TestValidateDNSCreate(t *testing.T) {
 func TestValidateDNSCreate(t *testing.T) {
-	database.InitializeDatabase()
 	_ = logic.DeleteDNS("mynode", "skynet")
 	_ = logic.DeleteDNS("mynode", "skynet")
 	t.Run("NoNetwork", func(t *testing.T) {
 	t.Run("NoNetwork", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "badnet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
@@ -384,13 +373,13 @@ func TestValidateDNSCreate(t *testing.T) {
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// })
 	// })
 	t.Run("BadAddress", func(t *testing.T) {
 	t.Run("BadAddress", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.256.1", Name: "myhost", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	})
 	t.Run("EmptyName", func(t *testing.T) {
 	t.Run("EmptyName", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -400,13 +389,13 @@ func TestValidateDNSCreate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 			name = name + "a"
 		}
 		}
-		entry := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: name, Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	})
 	t.Run("NameUnique", func(t *testing.T) {
 	t.Run("NameUnique", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "skynet"}
 		_, _ = logic.CreateDNS(entry)
 		_, _ = logic.CreateDNS(entry)
 		err := logic.ValidateDNSCreate(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)

+ 1 - 1
controllers/docs.go

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

+ 238 - 0
controllers/enrollmentkeys.go

@@ -0,0 +1,238 @@
+package controller
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/hostactions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+func enrollmentKeyHandlers(r *mux.Router) {
+	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(getEnrollmentKeys))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(deleteEnrollmentKey))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/v1/host/register/{token}", http.HandlerFunc(handleHostRegister)).Methods(http.MethodPost)
+}
+
+// swagger:route GET /api/v1/enrollment-keys enrollmentKeys getEnrollmentKeys
+//
+// Lists all EnrollmentKeys for admins.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: getEnrollmentKeysSlice
+func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
+	currentKeys, err := logic.GetAllEnrollmentKeys()
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to fetch enrollment keys: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	for i := range currentKeys {
+		currentKey := currentKeys[i]
+		if err = logic.Tokenize(currentKey, servercfg.GetAPIHost()); err != nil {
+			logger.Log(0, r.Header.Get("user"), "failed to get token values for keys:", err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+	}
+	// return JSON/API formatted keys
+	logger.Log(2, r.Header.Get("user"), "fetched enrollment keys")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(currentKeys)
+}
+
+// swagger:route DELETE /api/v1/enrollment-keys/{keyID} enrollmentKeys deleteEnrollmentKey
+//
+// Deletes an EnrollmentKey from Netmaker server.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: deleteEnrollmentKeyResponse
+func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	keyID := params["keyID"]
+	err := logic.DeleteEnrollmentKey(keyID)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to remove enrollment key: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	logger.Log(2, r.Header.Get("user"), "deleted enrollment key", keyID)
+	w.WriteHeader(http.StatusOK)
+}
+
+// swagger:route POST /api/v1/enrollment-keys enrollmentKeys createEnrollmentKey
+//
+// Creates an EnrollmentKey for hosts to use on Netmaker server.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: createEnrollmentKeyResponse
+func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
+
+	var enrollmentKeyBody models.APIEnrollmentKey
+
+	err := json.NewDecoder(r.Body).Decode(&enrollmentKeyBody)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	var newTime time.Time
+	if enrollmentKeyBody.Expiration > 0 {
+		newTime = time.Unix(enrollmentKeyBody.Expiration, 0)
+	}
+
+	newEnrollmentKey, err := logic.CreateEnrollmentKey(enrollmentKeyBody.UsesRemaining, newTime, enrollmentKeyBody.Networks, enrollmentKeyBody.Tags, enrollmentKeyBody.Unlimited)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
+	if err = logic.Tokenize(newEnrollmentKey, servercfg.GetAPIHost()); err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	logger.Log(2, r.Header.Get("user"), "created enrollment key")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(newEnrollmentKey)
+}
+
+// swagger:route POST /api/v1/enrollment-keys/{token} enrollmentKeys handleHostRegister
+//
+// Handles a Netclient registration with server and add nodes accordingly.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: handleHostRegisterResponse
+func handleHostRegister(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	token := params["token"]
+	logger.Log(0, "received registration attempt with token", token)
+	// check if token exists
+	enrollmentKey, err := logic.DeTokenize(token)
+	if err != nil {
+		logger.Log(0, "invalid enrollment key used", token, err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	// get the host
+	var newHost models.Host
+	if err = json.NewDecoder(r.Body).Decode(&newHost); err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	hostExists := false
+	// check if host already exists
+	if hostExists = logic.HostExists(&newHost); hostExists && len(enrollmentKey.Networks) == 0 {
+		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "attempted to re-register with no networks")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("host already exists"), "badrequest"))
+		return
+	}
+	// version check
+	if !logic.IsVersionComptatible(newHost.Version) || newHost.TrafficKeyPublic == nil {
+		err := fmt.Errorf("incompatible netclient")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	key, keyErr := logic.RetrievePublicTrafficKey()
+	if keyErr != nil {
+		logger.Log(0, "error retrieving key:", keyErr.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	// use the token
+	if ok := logic.TryToUseEnrollmentKey(enrollmentKey); !ok {
+		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("invalid enrollment key"), "badrequest"))
+		return
+	}
+	if !hostExists {
+		// register host
+		logic.CheckHostPorts(&newHost)
+		if err = logic.CreateHost(&newHost); err != nil {
+			logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration -", err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+	} else {
+		// need to revise the list of networks from key
+		// based on the ones host currently has
+		var networksToAdd = []string{}
+		currentNets := logic.GetHostNetworks(newHost.ID.String())
+		for _, newNet := range enrollmentKey.Networks {
+			if !logic.StringSliceContains(currentNets, newNet) {
+				networksToAdd = append(networksToAdd, newNet)
+			}
+		}
+		enrollmentKey.Networks = networksToAdd
+	}
+	// ready the response
+	server := servercfg.GetServerInfo()
+	server.TrafficKey = key
+	logger.Log(0, newHost.Name, newHost.ID.String(), "registered with Netmaker")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(&server)
+	// notify host of changes, peer and node updates
+	go checkNetRegAndHostUpdate(enrollmentKey.Networks, &newHost)
+}
+
+// run through networks and send a host update
+func checkNetRegAndHostUpdate(networks []string, h *models.Host) {
+	// publish host update through MQ
+	for i := range networks {
+		network := networks[i]
+		if ok, _ := logic.NetworkExists(network); ok {
+			newNode, err := logic.UpdateHostNetwork(h, network, true)
+			if err != nil {
+				logger.Log(0, "failed to add host to network:", h.ID.String(), h.Name, network, err.Error())
+				continue
+			}
+			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
+			hostactions.AddAction(models.HostUpdate{
+				Action: models.JoinHostToNetwork,
+				Host:   *h,
+				Node:   *newNode,
+			})
+		}
+	}
+	if servercfg.IsMessageQueueBackend() {
+		mq.HostUpdate(&models.HostUpdate{
+			Action: models.RequestAck,
+			Host:   *h,
+		})
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(0, "failed to publish peer update during registration -", err.Error())
+		}
+	}
+}

+ 28 - 12
controllers/ext_client.go

@@ -214,7 +214,7 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	if network.DefaultKeepalive != 0 {
 	if network.DefaultKeepalive != 0 {
 		keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
 		keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
 	}
 	}
-	gwendpoint := host.EndpointIP.String() + ":" + strconv.Itoa(host.ListenPort)
+	gwendpoint := host.EndpointIP.String() + ":" + strconv.Itoa(logic.GetPeerListenPort(host))
 	newAllowedIPs := network.AddressRange
 	newAllowedIPs := network.AddressRange
 	if newAllowedIPs != "" && network.AddressRange6 != "" {
 	if newAllowedIPs != "" && network.AddressRange6 != "" {
 		newAllowedIPs += ","
 		newAllowedIPs += ","
@@ -388,10 +388,15 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 
 
 	logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
 	logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
-	err = mq.PublishExtPeerUpdate(&node)
-	if err != nil {
-		logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
-	}
+	go func() {
+		err = mq.PublishPeerUpdate()
+		if err != nil {
+			logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
+		}
+		if err := mq.PublishExtCLientDNS(&extclient); err != nil {
+			logger.Log(1, "error publishing extclient dns", err.Error())
+		}
+	}()
 }
 }
 
 
 // swagger:route PUT /api/extclients/{network}/{clientid} ext_client updateExtClient
 // swagger:route PUT /api/extclients/{network}/{clientid} ext_client updateExtClient
@@ -459,7 +464,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-
 	if changedID && oldExtClient.OwnerID != "" {
 	if changedID && oldExtClient.OwnerID != "" {
 		if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, networkName, oldExtClient.ClientID); err != nil {
 		if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, networkName, oldExtClient.ClientID); err != nil {
 			logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
 			logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
@@ -471,7 +475,8 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	// == END PRO ==
 	// == END PRO ==
 
 
 	var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement
 	var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement
-
+	// extra var need as logic.Update changes oldExtClient
+	currentClient := oldExtClient
 	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient)
 	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
@@ -483,13 +488,20 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	logger.Log(0, r.Header.Get("user"), "updated ext client", newExtClient.ClientID)
 	logger.Log(0, r.Header.Get("user"), "updated ext client", newExtClient.ClientID)
 	if changedEnabled { // need to send a peer update to the ingress node as enablement of one of it's clients has changed
 	if changedEnabled { // need to send a peer update to the ingress node as enablement of one of it's clients has changed
 		if ingressNode, err := logic.GetNodeByID(newclient.IngressGatewayID); err == nil {
 		if ingressNode, err := logic.GetNodeByID(newclient.IngressGatewayID); err == nil {
-			if err = mq.PublishExtPeerUpdate(&ingressNode); err != nil {
+			if err = mq.PublishPeerUpdate(); err != nil {
 				logger.Log(1, "error setting ext peers on", ingressNode.ID.String(), ":", err.Error())
 				logger.Log(1, "error setting ext peers on", ingressNode.ID.String(), ":", err.Error())
 			}
 			}
 		}
 		}
 	}
 	}
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(newclient)
 	json.NewEncoder(w).Encode(newclient)
+	if changedID {
+		go func() {
+			if err := mq.PublishExtClientDNSUpdate(currentClient, newExtClient, networkName); err != nil {
+				logger.Log(1, "error pubishing dns update for extcient update", err.Error())
+			}
+		}()
+	}
 }
 }
 
 
 // swagger:route DELETE /api/extclients/{network}/{clientid} ext_client deleteExtClient
 // swagger:route DELETE /api/extclients/{network}/{clientid} ext_client deleteExtClient
@@ -554,10 +566,14 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	err = mq.PublishExtPeerUpdate(&ingressnode)
-	if err != nil {
-		logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
-	}
+	go func() {
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
+		}
+		if err = mq.PublishDeleteExtClientDNS(&extclient); err != nil {
+			logger.Log(1, "error publishing dns update for extclient deletion", err.Error())
+		}
+	}()
 
 
 	logger.Log(0, r.Header.Get("user"),
 	logger.Log(0, r.Header.Get("user"),
 		"Deleted extclient client", params["clientid"], "from network", params["network"])
 		"Deleted extclient client", params["clientid"], "from network", params["network"])

+ 28 - 8
controllers/hosts.go

@@ -10,8 +10,10 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/bcrypt"
 )
 )
 
 
@@ -108,6 +110,19 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 		if err := mq.PublishPeerUpdate(); err != nil {
 		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(0, "fail to publish peer update: ", err.Error())
 			logger.Log(0, "fail to publish peer update: ", err.Error())
 		}
 		}
+		if newHost.Name != currHost.Name {
+			networks := logic.GetHostNetworks(currHost.ID.String())
+			if err := mq.PublishHostDNSUpdate(currHost, newHost, networks); err != nil {
+				var dnsError *models.DNSError
+				if errors.Is(err, dnsError) {
+					for _, message := range err.(models.DNSError).ErrorStrings {
+						logger.Log(0, message)
+					}
+				} else {
+					logger.Log(0, err.Error())
+				}
+			}
+		}
 	}()
 	}()
 
 
 	apiHostData := newHost.ConvertNMHostToAPI()
 	apiHostData := newHost.ConvertNMHostToAPI()
@@ -217,18 +232,17 @@ func addHostToNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
 	logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
-	if err = mq.HostUpdate(&models.HostUpdate{
+	hostactions.AddAction(models.HostUpdate{
 		Action: models.JoinHostToNetwork,
 		Action: models.JoinHostToNetwork,
 		Host:   *currHost,
 		Host:   *currHost,
 		Node:   *newNode,
 		Node:   *newNode,
-	}); err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to update host to join network:", hostid, network, err.Error())
+	})
+	if servercfg.IsMessageQueueBackend() {
+		mq.HostUpdate(&models.HostUpdate{
+			Action: models.RequestAck,
+			Host:   *currHost,
+		})
 	}
 	}
-	go func() { // notify of peer change
-		if err := mq.PublishPeerUpdate(); err != nil {
-			logger.Log(1, "error publishing peer update ", err.Error())
-		}
-	}()
 
 
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("added host %s to network %s", currHost.Name, network))
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("added host %s to network %s", currHost.Name, network))
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
@@ -268,17 +282,23 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
+	node.Action = models.NODE_DELETE
+	node.PendingDelete = true
 	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
 	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
 	if err := logic.DeleteNode(node, false); err != nil {
 	if err := logic.DeleteNode(node, false); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 		return
 	}
 	}
 	// notify node change
 	// notify node change
+
 	runUpdates(node, false)
 	runUpdates(node, false)
 	go func() { // notify of peer change
 	go func() { // notify of peer change
 		if err := mq.PublishPeerUpdate(); err != nil {
 		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
 			logger.Log(1, "error publishing peer update ", err.Error())
 		}
 		}
+		if err := mq.PublishDNSDelete(node, currHost); err != nil {
+			logger.Log(1, "error publishing dns update", err.Error())
+		}
 	}()
 	}()
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("removed host %s from network %s", currHost.Name, network))
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("removed host %s from network %s", currHost.Name, network))
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)

+ 17 - 5
controllers/network.go

@@ -403,13 +403,25 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	if err = logic.AddDefaultHostsToNetwork(network.NetID, servercfg.GetServer()); err != nil {
-		logger.Log(0, fmt.Sprintf("failed to add default hosts to network [%v]: %v",
-			network.NetID, err.Error()))
+	defaultHosts := logic.GetDefaultHosts()
+	for i := range defaultHosts {
+		currHost := &defaultHosts[i]
+		newNode, err := logic.UpdateHostNetwork(currHost, network.NetID, true)
+		if err != nil {
+			logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+		logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
+		if err = mq.HostUpdate(&models.HostUpdate{
+			Action: models.JoinHostToNetwork,
+			Host:   *currHost,
+			Node:   *newNode,
+		}); err != nil {
+			logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
+		}
 	}
 	}
 
 
-	// TODO: Send message notifying host of new peers/network conf
-
 	logger.Log(1, r.Header.Get("user"), "created network", network.NetID)
 	logger.Log(1, r.Header.Get("user"), "created network", network.NetID)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(network)
 	json.NewEncoder(w).Encode(network)

+ 22 - 24
controllers/network_test.go

@@ -1,11 +1,13 @@
 package controller
 package controller
 
 
 import (
 import (
+	"context"
 	"os"
 	"os"
 	"testing"
 	"testing"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
@@ -20,8 +22,27 @@ type NetworkValidationTestCase struct {
 
 
 var netHost models.Host
 var netHost models.Host
 
 
+func TestMain(m *testing.M) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	logic.CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go logic.ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
 func TestCreateNetwork(t *testing.T) {
 func TestCreateNetwork(t *testing.T) {
-	initialize()
 	deleteAllNetworks()
 	deleteAllNetworks()
 
 
 	var network models.Network
 	var network models.Network
@@ -34,7 +55,6 @@ func TestCreateNetwork(t *testing.T) {
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 }
 }
 func TestGetNetwork(t *testing.T) {
 func TestGetNetwork(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 
 
 	t.Run("GetExistingNetwork", func(t *testing.T) {
 	t.Run("GetExistingNetwork", func(t *testing.T) {
@@ -50,7 +70,6 @@ func TestGetNetwork(t *testing.T) {
 }
 }
 
 
 func TestDeleteNetwork(t *testing.T) {
 func TestDeleteNetwork(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 	//create nodes
 	//create nodes
 	t.Run("NetworkwithNodes", func(t *testing.T) {
 	t.Run("NetworkwithNodes", func(t *testing.T) {
@@ -66,7 +85,6 @@ func TestDeleteNetwork(t *testing.T) {
 }
 }
 
 
 func TestCreateKey(t *testing.T) {
 func TestCreateKey(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 	keys, _ := logic.GetKeys("skynet")
 	keys, _ := logic.GetKeys("skynet")
 	for _, key := range keys {
 	for _, key := range keys {
@@ -138,7 +156,6 @@ func TestCreateKey(t *testing.T) {
 }
 }
 
 
 func TestGetKeys(t *testing.T) {
 func TestGetKeys(t *testing.T) {
-	initialize()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	network, err := logic.GetNetwork("skynet")
 	network, err := logic.GetNetwork("skynet")
@@ -161,7 +178,6 @@ func TestGetKeys(t *testing.T) {
 	})
 	})
 }
 }
 func TestDeleteKey(t *testing.T) {
 func TestDeleteKey(t *testing.T) {
-	initialize()
 	createNet()
 	createNet()
 	network, err := logic.GetNetwork("skynet")
 	network, err := logic.GetNetwork("skynet")
 	assert.Nil(t, err)
 	assert.Nil(t, err)
@@ -183,7 +199,6 @@ func TestDeleteKey(t *testing.T) {
 func TestSecurityCheck(t *testing.T) {
 func TestSecurityCheck(t *testing.T) {
 	//these seem to work but not sure it the tests are really testing the functionality
 	//these seem to work but not sure it the tests are really testing the functionality
 
 
-	initialize()
 	os.Setenv("MASTER_KEY", "secretkey")
 	os.Setenv("MASTER_KEY", "secretkey")
 	t.Run("NoNetwork", func(t *testing.T) {
 	t.Run("NoNetwork", func(t *testing.T) {
 		networks, username, err := logic.UserPermissions(false, "", "Bearer secretkey")
 		networks, username, err := logic.UserPermissions(false, "", "Bearer secretkey")
@@ -214,7 +229,6 @@ func TestValidateNetwork(t *testing.T) {
 	//t.Skip()
 	//t.Skip()
 	//This functions is not called by anyone
 	//This functions is not called by anyone
 	//it panics as validation function 'display_name_valid' is not defined
 	//it panics as validation function 'display_name_valid' is not defined
-	initialize()
 	//yes := true
 	//yes := true
 	//no := false
 	//no := false
 	//deleteNet(t)
 	//deleteNet(t)
@@ -291,7 +305,6 @@ func TestValidateNetwork(t *testing.T) {
 func TestIpv6Network(t *testing.T) {
 func TestIpv6Network(t *testing.T) {
 	//these seem to work but not sure it the tests are really testing the functionality
 	//these seem to work but not sure it the tests are really testing the functionality
 
 
-	initialize()
 	os.Setenv("MASTER_KEY", "secretkey")
 	os.Setenv("MASTER_KEY", "secretkey")
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
@@ -318,21 +331,6 @@ func deleteAllNetworks() {
 	}
 	}
 }
 }
 
 
-func initialize() {
-	database.InitializeDatabase()
-	createAdminUser()
-}
-
-func createAdminUser() {
-	logic.CreateAdmin(&models.User{
-		UserName: "admin",
-		Password: "password",
-		IsAdmin:  true,
-		Networks: []string{},
-		Groups:   []string{},
-	})
-}
-
 func createNet() {
 func createNet() {
 	var network models.Network
 	var network models.Network
 	network.NetID = "skynet"
 	network.NetID = "skynet"

+ 58 - 25
controllers/node.go

@@ -415,7 +415,6 @@ func getUsersNodes(user models.User) ([]models.Node, error) {
 func getNode(w http.ResponseWriter, r *http.Request) {
 func getNode(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
-
 	nodeRequest := r.Header.Get("requestfrom") == "node"
 	nodeRequest := r.Header.Get("requestfrom") == "node"
 
 
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
@@ -434,14 +433,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	peerUpdate, err := logic.GetPeerUpdate(&node, host)
-	if err != nil && !database.IsEmptyRecord(err) {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("error fetching wg peers config for node [ %s ]: %v", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(host)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(node.Network, host, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
@@ -449,21 +441,13 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	server := servercfg.GetServerInfo()
 	server := servercfg.GetServerInfo()
-	network, err := logic.GetNetwork(node.Network)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("error fetching network for node [ %s ] info: %v", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	legacy := node.Legacy(host, &server, &network)
 	response := models.NodeGet{
 	response := models.NodeGet{
-		Node:         *legacy,
+		Node:         node,
 		Host:         *host,
 		Host:         *host,
-		Peers:        peerUpdate.Peers,
 		HostPeers:    hostPeerUpdate.Peers,
 		HostPeers:    hostPeerUpdate.Peers,
+		Peers:        hostPeerUpdate.NodePeers,
 		ServerConfig: server,
 		ServerConfig: server,
-		PeerIDs:      peerUpdate.PeerIDs,
+		PeerIDs:      hostPeerUpdate.PeerIDs,
 	}
 	}
 
 
 	if servercfg.Is_EE && nodeRequest {
 	if servercfg.Is_EE && nodeRequest {
@@ -580,6 +564,13 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 	data.Node.Server = servercfg.GetServer()
 	data.Node.Server = servercfg.GetServer()
 	if !logic.HostExists(&data.Host) {
 	if !logic.HostExists(&data.Host) {
 		logic.CheckHostPorts(&data.Host)
 		logic.CheckHostPorts(&data.Host)
+		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+			// create EMQX credentials for host if it doesn't exists
+			if err := mq.CreateEmqxUser(data.Host.ID.String(), data.Host.HostPass, false); err != nil {
+				logger.Log(0, "failed to add host credentials to EMQX: ", data.Host.ID.String(), err.Error())
+				return
+			}
+		}
 	}
 	}
 	if err := logic.CreateHost(&data.Host); err != nil {
 	if err := logic.CreateHost(&data.Host); err != nil {
 		if errors.Is(err, logic.ErrHostExists) {
 		if errors.Is(err, logic.ErrHostExists) {
@@ -605,7 +596,6 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-
 	err = logic.AssociateNodeToHost(&data.Node, &data.Host)
 	err = logic.AssociateNodeToHost(&data.Node, &data.Host)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
@@ -632,7 +622,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(&data.Host)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(networkName, &data.Host, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", data.Host.ID.String(), err))
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", data.Host.ID.String(), err))
@@ -657,6 +647,30 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}()
 	}()
 	//runForceServerUpdate(&data.Node, true)
 	//runForceServerUpdate(&data.Node, true)
+	go func() {
+		dns := models.DNSUpdate{
+			Action: models.DNSInsert,
+			Name:   data.Host.Name + "." + data.Node.Network,
+		}
+		if data.Node.Address.IP != nil {
+			dns.Address = data.Node.Address.IP.String()
+			//publish new node dns entry to all nodes on network
+			if err := mq.PublishDNSUpdate(data.Node.Network, dns); err != nil {
+				logger.Log(1, "failed to publish dns update on node creation", err.Error())
+			}
+		}
+		if data.Node.Address6.IP != nil {
+			dns.Address = data.Node.Address6.IP.String()
+			//publish new node dns entry to all nodes on network
+			if err := mq.PublishDNSUpdate(data.Node.Network, dns); err != nil {
+				logger.Log(1, "failed to publish dns update on node creation", err.Error())
+			}
+		}
+		//publish add dns records for network to new node
+		if err := mq.PublishAllDNS(&data.Node); err != nil {
+			logger.Log(1, "failed to publish dns update on node creation", err.Error())
+		}
+	}()
 }
 }
 
 
 // == EGRESS ==
 // == EGRESS ==
@@ -929,6 +943,11 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiNode)
 	json.NewEncoder(w).Encode(apiNode)
 
 
 	runUpdates(newNode, ifaceDelta)
 	runUpdates(newNode, ifaceDelta)
+	go func() {
+		if err := mq.PublishReplaceDNS(&currentNode, newNode, host); err != nil {
+			logger.Log(1, "failed to publish dns update", err.Error())
+		}
+	}()
 }
 }
 
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
@@ -972,11 +991,25 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	if !fromNode { // notify node change
 	if !fromNode { // notify node change
 		runUpdates(&node, false)
 		runUpdates(&node, false)
 	}
 	}
-	go func() { // notify of peer change
-		if err := mq.PublishPeerUpdate(); err != nil {
+	go func(deletedNode *models.Node, fromNode bool) { // notify of peer change
+		var err error
+		if fromNode {
+			err = mq.PublishDeletedNodePeerUpdate(deletedNode)
+		} else {
+			err = mq.PublishPeerUpdate()
+		}
+		if err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
 			logger.Log(1, "error publishing peer update ", err.Error())
 		}
 		}
-	}()
+
+		host, err := logic.GetHost(node.HostID.String())
+		if err != nil {
+			logger.Log(1, "failed to retrieve host for node", node.ID.String(), err.Error())
+		}
+		if err := mq.PublishDNSDelete(&node, host); err != nil {
+			logger.Log(1, "error publishing dns update", err.Error())
+		}
+	}(&node, fromNode)
 }
 }
 
 
 func runUpdates(node *models.Node, ifaceDelta bool) {
 func runUpdates(node *models.Node, ifaceDelta bool) {

+ 0 - 3
controllers/node_test.go

@@ -21,7 +21,6 @@ func TestCreateEgressGateway(t *testing.T) {
 	var gateway models.EgressGatewayRequest
 	var gateway models.EgressGatewayRequest
 	gateway.Ranges = []string{"10.100.100.0/24"}
 	gateway.Ranges = []string{"10.100.100.0/24"}
 	gateway.NetID = "skynet"
 	gateway.NetID = "skynet"
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	t.Run("NoNodes", func(t *testing.T) {
 	t.Run("NoNodes", func(t *testing.T) {
@@ -78,7 +77,6 @@ func TestCreateEgressGateway(t *testing.T) {
 }
 }
 func TestDeleteEgressGateway(t *testing.T) {
 func TestDeleteEgressGateway(t *testing.T) {
 	var gateway models.EgressGatewayRequest
 	var gateway models.EgressGatewayRequest
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	testnode := createTestNode()
 	testnode := createTestNode()
@@ -110,7 +108,6 @@ func TestDeleteEgressGateway(t *testing.T) {
 }
 }
 
 
 func TestGetNetworkNodes(t *testing.T) {
 func TestGetNetworkNodes(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	deleteAllNetworks()
 	createNet()
 	createNet()
 	t.Run("BadNet", func(t *testing.T) {
 	t.Run("BadNet", func(t *testing.T) {

+ 62 - 49
controllers/user_test.go

@@ -3,22 +3,24 @@ package controller
 import (
 import (
 	"testing"
 	"testing"
 
 
-	"github.com/gravitl/netmaker/database"
+	"github.com/stretchr/testify/assert"
+
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
-	"github.com/stretchr/testify/assert"
 )
 )
 
 
-func deleteAllUsers() {
+func deleteAllUsers(t *testing.T) {
+	t.Helper()
 	users, _ := logic.GetUsers()
 	users, _ := logic.GetUsers()
 	for _, user := range users {
 	for _, user := range users {
-		logic.DeleteUser(user.UserName)
+		if _, err := logic.DeleteUser(user.UserName); err != nil {
+			t.Fatal(err)
+		}
 	}
 	}
 }
 }
 
 
 func TestHasAdmin(t *testing.T) {
 func TestHasAdmin(t *testing.T) {
-	//delete all current users
-	database.InitializeDatabase()
+	// delete all current users
 	users, _ := logic.GetUsers()
 	users, _ := logic.GetUsers()
 	for _, user := range users {
 	for _, user := range users {
 		success, err := logic.DeleteUser(user.UserName)
 		success, err := logic.DeleteUser(user.UserName)
@@ -31,7 +33,7 @@ func TestHasAdmin(t *testing.T) {
 		assert.False(t, found)
 		assert.False(t, found)
 	})
 	})
 	t.Run("No admin user", func(t *testing.T) {
 	t.Run("No admin user", func(t *testing.T) {
-		var user = models.User{"noadmin", "password", nil, false, nil}
+		var user = models.User{UserName: "noadmin", Password: "password"}
 		err := logic.CreateUser(&user)
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		found, err := logic.HasAdmin()
@@ -39,7 +41,7 @@ func TestHasAdmin(t *testing.T) {
 		assert.False(t, found)
 		assert.False(t, found)
 	})
 	})
 	t.Run("admin user", func(t *testing.T) {
 	t.Run("admin user", func(t *testing.T) {
-		var user = models.User{"admin", "password", nil, true, nil}
+		var user = models.User{UserName: "admin", Password: "password", IsAdmin: true}
 		err := logic.CreateUser(&user)
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		found, err := logic.HasAdmin()
@@ -47,8 +49,8 @@ func TestHasAdmin(t *testing.T) {
 		assert.True(t, found)
 		assert.True(t, found)
 	})
 	})
 	t.Run("multiple admins", func(t *testing.T) {
 	t.Run("multiple admins", func(t *testing.T) {
-		var user = models.User{"admin1", "password", nil, true, nil}
-		 err := logic.CreateUser(&user)
+		var user = models.User{UserName: "admin1", Password: "password", IsAdmin: true}
+		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
@@ -57,9 +59,8 @@ func TestHasAdmin(t *testing.T) {
 }
 }
 
 
 func TestCreateUser(t *testing.T) {
 func TestCreateUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
-	user := models.User{"admin", "password", nil, true, nil}
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
 	t.Run("NoUser", func(t *testing.T) {
 	t.Run("NoUser", func(t *testing.T) {
 		err := logic.CreateUser(&user)
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
@@ -72,8 +73,7 @@ func TestCreateUser(t *testing.T) {
 }
 }
 
 
 func TestCreateAdmin(t *testing.T) {
 func TestCreateAdmin(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
 	var user models.User
 	var user models.User
 	t.Run("NoAdmin", func(t *testing.T) {
 	t.Run("NoAdmin", func(t *testing.T) {
 		user.UserName = "admin"
 		user.UserName = "admin"
@@ -90,16 +90,17 @@ func TestCreateAdmin(t *testing.T) {
 }
 }
 
 
 func TestDeleteUser(t *testing.T) {
 func TestDeleteUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
 	t.Run("NonExistent User", func(t *testing.T) {
 	t.Run("NonExistent User", func(t *testing.T) {
 		deleted, err := logic.DeleteUser("admin")
 		deleted, err := logic.DeleteUser("admin")
 		assert.EqualError(t, err, "user does not exist")
 		assert.EqualError(t, err, "user does not exist")
 		assert.False(t, deleted)
 		assert.False(t, deleted)
 	})
 	})
 	t.Run("Existing User", func(t *testing.T) {
 	t.Run("Existing User", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Fatal(err)
+		}
 		deleted, err := logic.DeleteUser("admin")
 		deleted, err := logic.DeleteUser("admin")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.True(t, deleted)
 		assert.True(t, deleted)
@@ -107,7 +108,6 @@ func TestDeleteUser(t *testing.T) {
 }
 }
 
 
 func TestValidateUser(t *testing.T) {
 func TestValidateUser(t *testing.T) {
-	database.InitializeDatabase()
 	var user models.User
 	var user models.User
 	t.Run("Valid Create", func(t *testing.T) {
 	t.Run("Valid Create", func(t *testing.T) {
 		user.UserName = "admin"
 		user.UserName = "admin"
@@ -126,21 +126,21 @@ func TestValidateUser(t *testing.T) {
 		user.UserName = "*invalid"
 		user.UserName = "*invalid"
 		err := logic.ValidateUser(&user)
 		err := logic.ValidateUser(&user)
 		assert.Error(t, err)
 		assert.Error(t, err)
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	})
 	t.Run("Short UserName", func(t *testing.T) {
 	t.Run("Short UserName", func(t *testing.T) {
 		t.Skip()
 		t.Skip()
 		user.UserName = "1"
 		user.UserName = "1"
 		err := logic.ValidateUser(&user)
 		err := logic.ValidateUser(&user)
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	})
 	t.Run("Empty UserName", func(t *testing.T) {
 	t.Run("Empty UserName", func(t *testing.T) {
 		t.Skip()
 		t.Skip()
 		user.UserName = ""
 		user.UserName = ""
 		err := logic.ValidateUser(&user)
 		err := logic.ValidateUser(&user)
 		assert.EqualError(t, err, "some string")
 		assert.EqualError(t, err, "some string")
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	})
 	t.Run("EmptyPassword", func(t *testing.T) {
 	t.Run("EmptyPassword", func(t *testing.T) {
 		user.Password = ""
 		user.Password = ""
@@ -155,16 +155,19 @@ func TestValidateUser(t *testing.T) {
 }
 }
 
 
 func TestGetUser(t *testing.T) {
 func TestGetUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+
+	user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
+
 	t.Run("NonExistantUser", func(t *testing.T) {
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUser("admin")
 		admin, err := logic.GetUser("admin")
 		assert.EqualError(t, err, "could not find any records")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, "", admin.UserName)
 		assert.Equal(t, "", admin.UserName)
 	})
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 	t.Run("UserExisits", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admin, err := logic.GetUser("admin")
 		admin, err := logic.GetUser("admin")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admin.UserName)
 		assert.Equal(t, user.UserName, admin.UserName)
@@ -172,30 +175,36 @@ func TestGetUser(t *testing.T) {
 }
 }
 
 
 func TestGetUsers(t *testing.T) {
 func TestGetUsers(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+
+	adminUser := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: false}
+
 	t.Run("NonExistantUser", func(t *testing.T) {
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUsers()
 		admin, err := logic.GetUsers()
 		assert.EqualError(t, err, "could not find any records")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 	})
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 	t.Run("UserExisits", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&adminUser); err != nil {
+			t.Error(err)
+		}
 		admins, err := logic.GetUsers()
 		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		assert.Equal(t, user.UserName, admins[0].UserName)
+		assert.Equal(t, adminUser.UserName, admins[0].UserName)
 	})
 	})
 	t.Run("MulipleUsers", func(t *testing.T) {
 	t.Run("MulipleUsers", func(t *testing.T) {
-		user := models.User{"user", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admins, err := logic.GetUsers()
 		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		for _, u := range admins {
 		for _, u := range admins {
 			if u.UserName == "admin" {
 			if u.UserName == "admin" {
-				assert.Equal(t, "admin", u.UserName)
+				assert.Equal(t, true, u.IsAdmin)
 			} else {
 			} else {
 				assert.Equal(t, user.UserName, u.UserName)
 				assert.Equal(t, user.UserName, u.UserName)
+				assert.Equal(t, user.IsAdmin, u.IsAdmin)
 			}
 			}
 		}
 		}
 	})
 	})
@@ -203,10 +212,9 @@ func TestGetUsers(t *testing.T) {
 }
 }
 
 
 func TestUpdateUser(t *testing.T) {
 func TestUpdateUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
-	user := models.User{"admin", "password", nil, true, nil}
-	newuser := models.User{"hello", "world", []string{"wirecat, netmaker"}, true, []string{}}
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+	newuser := models.User{UserName: "hello", Password: "world", Networks: []string{"wirecat, netmaker"}, IsAdmin: true, Groups: []string{}}
 	t.Run("NonExistantUser", func(t *testing.T) {
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.UpdateUser(&newuser, &user)
 		admin, err := logic.UpdateUser(&newuser, &user)
 		assert.EqualError(t, err, "could not find any records")
 		assert.EqualError(t, err, "could not find any records")
@@ -214,7 +222,9 @@ func TestUpdateUser(t *testing.T) {
 	})
 	})
 
 
 	t.Run("UserExists", func(t *testing.T) {
 	t.Run("UserExists", func(t *testing.T) {
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admin, err := logic.UpdateUser(&newuser, &user)
 		admin, err := logic.UpdateUser(&newuser, &user)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.Equal(t, newuser.UserName, admin.UserName)
 		assert.Equal(t, newuser.UserName, admin.UserName)
@@ -246,8 +256,8 @@ func TestUpdateUser(t *testing.T) {
 // }
 // }
 
 
 func TestVerifyAuthRequest(t *testing.T) {
 func TestVerifyAuthRequest(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
 	var authRequest models.UserAuthParams
 	var authRequest models.UserAuthParams
 	t.Run("EmptyUserName", func(t *testing.T) {
 	t.Run("EmptyUserName", func(t *testing.T) {
 		authRequest.UserName = ""
 		authRequest.UserName = ""
@@ -271,23 +281,26 @@ func TestVerifyAuthRequest(t *testing.T) {
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 	})
 	})
 	t.Run("Non-Admin", func(t *testing.T) {
 	t.Run("Non-Admin", func(t *testing.T) {
-		user := models.User{"nonadmin", "somepass", nil, false, []string{}}
-		logic.CreateUser(&user)
-		authRequest := models.UserAuthParams{"nonadmin", "somepass"}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
+		authRequest := models.UserAuthParams{UserName: "nonadmin", Password: "somepass"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.NotNil(t, jwt)
 		assert.NotNil(t, jwt)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
 	t.Run("WrongPassword", func(t *testing.T) {
 	t.Run("WrongPassword", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, false, []string{}}
-		logic.CreateUser(&user)
-		authRequest := models.UserAuthParams{"admin", "badpass"}
+		user := models.User{UserName: "admin", Password: "password", Groups: []string{}}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
+		authRequest := models.UserAuthParams{UserName: "admin", Password: "badpass"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Equal(t, "", jwt)
 		assert.Equal(t, "", jwt)
 		assert.EqualError(t, err, "incorrect credentials")
 		assert.EqualError(t, err, "incorrect credentials")
 	})
 	})
 	t.Run("Success", func(t *testing.T) {
 	t.Run("Success", func(t *testing.T) {
-		authRequest := models.UserAuthParams{"admin", "password"}
+		authRequest := models.UserAuthParams{UserName: "admin", Password: "password"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 		assert.NotNil(t, jwt)
 		assert.NotNil(t, jwt)

+ 3 - 0
database/database.go

@@ -57,6 +57,8 @@ const (
 	CACHE_TABLE_NAME = "cache"
 	CACHE_TABLE_NAME = "cache"
 	// HOSTS_TABLE_NAME - the table name for hosts
 	// HOSTS_TABLE_NAME - the table name for hosts
 	HOSTS_TABLE_NAME = "hosts"
 	HOSTS_TABLE_NAME = "hosts"
+	// ENROLLMENT_KEYS_TABLE_NAME - table name for enrollmentkeys
+	ENROLLMENT_KEYS_TABLE_NAME = "enrollmentkeys"
 
 
 	// == ERROR CONSTS ==
 	// == ERROR CONSTS ==
 	// NO_RECORD - no singular result found
 	// NO_RECORD - no singular result found
@@ -138,6 +140,7 @@ func createTables() {
 	createTable(USER_GROUPS_TABLE_NAME)
 	createTable(USER_GROUPS_TABLE_NAME)
 	createTable(CACHE_TABLE_NAME)
 	createTable(CACHE_TABLE_NAME)
 	createTable(HOSTS_TABLE_NAME)
 	createTable(HOSTS_TABLE_NAME)
+	createTable(ENROLLMENT_KEYS_TABLE_NAME)
 }
 }
 
 
 func createTable(tableName string) error {
 func createTable(tableName string) error {

+ 0 - 7
dev.yaml

@@ -3,17 +3,12 @@ server:
   apiconn: "api.ping.clustercat.com:443"
   apiconn: "api.ping.clustercat.com:443"
   apihost: ""
   apihost: ""
   apiport: "8081"
   apiport: "8081"
-  grpcconn: "grpc.ping.clustercat.com:443"
-  grpchost: ""
-  grpcport: "50051"
-  grpcsecure: "on"
   mqhost: "localhost"
   mqhost: "localhost"
   masterkey: "secretkey"
   masterkey: "secretkey"
   dnskey: ""
   dnskey: ""
   allowedorigin: "*"
   allowedorigin: "*"
   nodeid: "netmaker"
   nodeid: "netmaker"
   restbackend: "on"
   restbackend: "on"
-  agentbackend: "on"
   messagequeuebackend: "on"
   messagequeuebackend: "on"
   dnsmode: "on"
   dnsmode: "on"
   disableremoteipcheck: ""
   disableremoteipcheck: ""
@@ -22,9 +17,7 @@ server:
   sqlconn: ""
   sqlconn: ""
   platform: ""
   platform: ""
   database: "sqlite"
   database: "sqlite"
-  defaultnodelimit: ""
   verbosity: 3
   verbosity: 3
-  servercheckininterval: ""
   authprovider: ""
   authprovider: ""
   clientid: ""
   clientid: ""
   clientsecret: ""
   clientsecret: ""

+ 1 - 1
docker/Caddyfile

@@ -38,5 +38,5 @@ https://stun.NETMAKER_BASE_DOMAIN {
 
 
 # MQ
 # MQ
 wss://broker.NETMAKER_BASE_DOMAIN {
 wss://broker.NETMAKER_BASE_DOMAIN {
-        reverse_proxy ws://mq:8883
+        reverse_proxy ws://mq:8883 # For EMQX websockets use `reverse_proxy ws://mq:8083`
 }
 }

+ 2 - 1
docker/Caddyfile-EE

@@ -1,5 +1,6 @@
 {
 {
-        # LetsEncrypt account
+        # ZeroSSL account
+        acme_ca https://acme.zerossl.com/v2/DV90
         email YOUR_EMAIL
         email YOUR_EMAIL
 }
 }
 
 

+ 22 - 8
functions/helpers_test.go

@@ -1,10 +1,12 @@
 package functions
 package functions
 
 
 import (
 import (
+	"context"
 	"encoding/json"
 	"encoding/json"
 	"testing"
 	"testing"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 )
 )
@@ -19,11 +21,27 @@ var (
 	}
 	}
 )
 )
 
 
+func TestMain(m *testing.M) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	logic.CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go logic.ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
 func TestNetworkExists(t *testing.T) {
 func TestNetworkExists(t *testing.T) {
-	err := database.InitializeDatabase()
-	if err != nil {
-		t.Fatalf("error initilizing database: %s", err)
-	}
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
 	defer database.CloseDB()
 	defer database.CloseDB()
 	exists, err := logic.NetworkExists(testNetwork.NetID)
 	exists, err := logic.NetworkExists(testNetwork.NetID)
@@ -53,10 +71,6 @@ func TestNetworkExists(t *testing.T) {
 }
 }
 
 
 func TestGetAllExtClients(t *testing.T) {
 func TestGetAllExtClients(t *testing.T) {
-	err := database.InitializeDatabase()
-	if err != nil {
-		t.Fatalf("error initilizing database: %s", err)
-	}
 	defer database.CloseDB()
 	defer database.CloseDB()
 	database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
 	database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
 
 

+ 9 - 10
go.mod

@@ -4,8 +4,8 @@ go 1.19
 
 
 require (
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.2
 	github.com/eclipse/paho.mqtt.golang v1.4.2
-	github.com/go-playground/validator/v10 v10.11.1
-	github.com/golang-jwt/jwt/v4 v4.4.3
+	github.com/go-playground/validator/v10 v10.11.2
+	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.3.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/mux v1.8.0
@@ -15,11 +15,11 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.1
 	github.com/stretchr/testify v1.8.1
 	github.com/txn2/txeh v1.3.0
 	github.com/txn2/txeh v1.3.0
-	golang.org/x/crypto v0.3.0
-	golang.org/x/net v0.4.0 // indirect
-	golang.org/x/oauth2 v0.3.0
-	golang.org/x/sys v0.3.0 // indirect
-	golang.org/x/text v0.5.0 // indirect
+	golang.org/x/crypto v0.6.0
+	golang.org/x/net v0.6.0 // indirect
+	golang.org/x/oauth2 v0.5.0
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -29,7 +29,6 @@ require (
 require (
 require (
 	filippo.io/edwards25519 v1.0.0
 	filippo.io/edwards25519 v1.0.0
 	github.com/c-robinson/iplib v1.0.6
 	github.com/c-robinson/iplib v1.0.6
-	github.com/go-ping/ping v1.1.0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 )
 )
 
 
@@ -60,8 +59,8 @@ require (
 	cloud.google.com/go/compute v1.12.1 // indirect
 	cloud.google.com/go/compute v1.12.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
-	github.com/go-playground/locales v0.14.0 // indirect
-	github.com/go-playground/universal-translator v0.18.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/hashicorp/go-version v1.6.0
 	github.com/hashicorp/go-version v1.6.0

+ 20 - 34
go.sum

@@ -17,7 +17,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -29,18 +28,15 @@ github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
-github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
-github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
-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.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
-github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
-github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
-github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
@@ -52,7 +48,6 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-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 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -73,13 +68,10 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 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/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
@@ -106,7 +98,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -116,9 +107,7 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -160,10 +149,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-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-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -172,17 +160,18 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 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-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 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-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
+golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
+golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
+golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -193,10 +182,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -205,8 +192,9 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -215,8 +203,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -240,11 +229,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 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-20180628173108-788fd7840127/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 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=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gortc.io/stun v1.23.0 h1:CpRQFjakCZMwVKTwInKbcCzlBklj62LGzD3NPdFyGrE=
 gortc.io/stun v1.23.0 h1:CpRQFjakCZMwVKTwInKbcCzlBklj62LGzD3NPdFyGrE=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

+ 5 - 7
k8s/server/netmaker-server.yaml

@@ -71,17 +71,15 @@ spec:
           value: REPLACE_MASTER_KEY
           value: REPLACE_MASTER_KEY
         - name: CORS_ALLOWED_ORIGIN
         - name: CORS_ALLOWED_ORIGIN
           value: '*'
           value: '*'
-        - name: MQ_HOST
-          value: "mq"
-        - name: MQ_PORT
-          value: "443"
-        - name: MQ_SERVER_PORT
-          value: "1883"
+        - name: SERVER_BROKER_ENDPOINT
+          value: "ws://mq:1883"
+        - name: BROKER_ENDPOINT
+          value: "wss://broker.NETMAKER_BASE_DOMAIN"
         - name: PLATFORM
         - name: PLATFORM
           value: "Kubernetes"
           value: "Kubernetes"
         - name: VERBOSITY
         - name: VERBOSITY
           value: "3"
           value: "3"
-        image: gravitl/netmaker:v0.18.0
+        image: gravitl/netmaker:v0.18.2
         imagePullPolicy: Always
         imagePullPolicy: Always
         name: netmaker
         name: netmaker
         ports:
         ports:

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

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

+ 0 - 2
logic/accesskeys_test.go

@@ -5,7 +5,6 @@ import "testing"
 func Test_genKeyName(t *testing.T) {
 func Test_genKeyName(t *testing.T) {
 	for i := 0; i < 100; i++ {
 	for i := 0; i < 100; i++ {
 		kname := genKeyName()
 		kname := genKeyName()
-		t.Log(kname)
 		if len(kname) != 20 {
 		if len(kname) != 20 {
 			t.Fatalf("improper length of key name, expected 20 got :%d", len(kname))
 			t.Fatalf("improper length of key name, expected 20 got :%d", len(kname))
 		}
 		}
@@ -15,7 +14,6 @@ func Test_genKeyName(t *testing.T) {
 func Test_genKey(t *testing.T) {
 func Test_genKey(t *testing.T) {
 	for i := 0; i < 100; i++ {
 	for i := 0; i < 100; i++ {
 		kname := GenKey()
 		kname := GenKey()
-		t.Log(kname)
 		if len(kname) != 16 {
 		if len(kname) != 16 {
 			t.Fatalf("improper length of key name, expected 16 got :%d", len(kname))
 			t.Fatalf("improper length of key name, expected 16 got :%d", len(kname))
 		}
 		}

+ 221 - 0
logic/enrollmentkey.go

@@ -0,0 +1,221 @@
+package logic
+
+import (
+	b64 "encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+// EnrollmentErrors - struct for holding EnrollmentKey error messages
+var EnrollmentErrors = struct {
+	InvalidCreate      error
+	NoKeyFound         error
+	InvalidKey         error
+	NoUsesRemaining    error
+	FailedToTokenize   error
+	FailedToDeTokenize error
+}{
+	InvalidCreate:      fmt.Errorf("invalid enrollment key created"),
+	NoKeyFound:         fmt.Errorf("no enrollmentkey found"),
+	InvalidKey:         fmt.Errorf("invalid key provided"),
+	NoUsesRemaining:    fmt.Errorf("no uses remaining"),
+	FailedToTokenize:   fmt.Errorf("failed to tokenize"),
+	FailedToDeTokenize: fmt.Errorf("failed to detokenize"),
+}
+
+// CreateEnrollmentKey - creates a new enrollment key in db
+func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, unlimited bool) (k *models.EnrollmentKey, err error) {
+	newKeyID, err := getUniqueEnrollmentID()
+	if err != nil {
+		return nil, err
+	}
+	k = &models.EnrollmentKey{
+		Value:         newKeyID,
+		Expiration:    time.Time{},
+		UsesRemaining: 0,
+		Unlimited:     unlimited,
+		Networks:      []string{},
+		Tags:          []string{},
+	}
+	if uses > 0 {
+		k.UsesRemaining = uses
+	}
+	if !expiration.IsZero() {
+		k.Expiration = expiration
+	}
+	if len(networks) > 0 {
+		k.Networks = networks
+	}
+	if len(tags) > 0 {
+		k.Tags = tags
+	}
+	if ok := k.Validate(); !ok {
+		return nil, EnrollmentErrors.InvalidCreate
+	}
+	if err = upsertEnrollmentKey(k); err != nil {
+		return nil, err
+	}
+	return
+}
+
+// GetAllEnrollmentKeys - fetches all enrollment keys from DB
+func GetAllEnrollmentKeys() ([]*models.EnrollmentKey, error) {
+	currentKeys, err := getEnrollmentKeysMap()
+	if err != nil {
+		return nil, err
+	}
+	var currentKeysList = []*models.EnrollmentKey{}
+	for k := range currentKeys {
+		currentKeysList = append(currentKeysList, currentKeys[k])
+	}
+	return currentKeysList, nil
+}
+
+// GetEnrollmentKey - fetches a single enrollment key
+// returns nil and error if not found
+func GetEnrollmentKey(value string) (*models.EnrollmentKey, error) {
+	currentKeys, err := getEnrollmentKeysMap()
+	if err != nil {
+		return nil, err
+	}
+	if key, ok := currentKeys[value]; ok {
+		return key, nil
+	}
+	return nil, EnrollmentErrors.NoKeyFound
+}
+
+// DeleteEnrollmentKey - delete's a given enrollment key by value
+func DeleteEnrollmentKey(value string) error {
+	_, err := GetEnrollmentKey(value)
+	if err != nil {
+		return err
+	}
+	return database.DeleteRecord(database.ENROLLMENT_KEYS_TABLE_NAME, value)
+}
+
+// TryToUseEnrollmentKey - checks first if key can be decremented
+// returns true if it is decremented or isvalid
+func TryToUseEnrollmentKey(k *models.EnrollmentKey) bool {
+	key, err := decrementEnrollmentKey(k.Value)
+	if err != nil {
+		if errors.Is(err, EnrollmentErrors.NoUsesRemaining) {
+			return k.IsValid()
+		}
+	} else {
+		k.UsesRemaining = key.UsesRemaining
+		return true
+	}
+	return false
+}
+
+// Tokenize - tokenizes an enrollment key to be used via registration
+// and attaches it to the Token field on the struct
+func Tokenize(k *models.EnrollmentKey, serverAddr string) error {
+	if len(serverAddr) == 0 || k == nil {
+		return EnrollmentErrors.FailedToTokenize
+	}
+	newToken := models.EnrollmentToken{
+		Server: serverAddr,
+		Value:  k.Value,
+	}
+	data, err := json.Marshal(&newToken)
+	if err != nil {
+		return err
+	}
+	k.Token = b64.StdEncoding.EncodeToString(data)
+	return nil
+}
+
+// DeTokenize - detokenizes a base64 encoded string
+// and finds the associated enrollment key
+func DeTokenize(b64Token string) (*models.EnrollmentKey, error) {
+	if len(b64Token) == 0 {
+		return nil, EnrollmentErrors.FailedToDeTokenize
+	}
+	tokenData, err := b64.StdEncoding.DecodeString(b64Token)
+	if err != nil {
+		return nil, err
+	}
+
+	var newToken models.EnrollmentToken
+	err = json.Unmarshal(tokenData, &newToken)
+	if err != nil {
+		return nil, err
+	}
+	k, err := GetEnrollmentKey(newToken.Value)
+	if err != nil {
+		return nil, err
+	}
+	return k, nil
+}
+
+// == private ==
+
+// decrementEnrollmentKey - decrements the uses on a key if above 0 remaining
+func decrementEnrollmentKey(value string) (*models.EnrollmentKey, error) {
+	k, err := GetEnrollmentKey(value)
+	if err != nil {
+		return nil, err
+	}
+	if k.UsesRemaining == 0 {
+		return nil, EnrollmentErrors.NoUsesRemaining
+	}
+	k.UsesRemaining = k.UsesRemaining - 1
+	if err = upsertEnrollmentKey(k); err != nil {
+		return nil, err
+	}
+
+	return k, nil
+}
+
+func upsertEnrollmentKey(k *models.EnrollmentKey) error {
+	if k == nil {
+		return EnrollmentErrors.InvalidKey
+	}
+	data, err := json.Marshal(k)
+	if err != nil {
+		return err
+	}
+	return database.Insert(k.Value, string(data), database.ENROLLMENT_KEYS_TABLE_NAME)
+}
+
+func getUniqueEnrollmentID() (string, error) {
+	currentKeys, err := getEnrollmentKeysMap()
+	if err != nil {
+		return "", err
+	}
+	newID := ncutils.MakeRandomString(models.EnrollmentKeyLength)
+	for _, ok := currentKeys[newID]; ok; {
+		newID = ncutils.MakeRandomString(models.EnrollmentKeyLength)
+	}
+	return newID, nil
+}
+
+func getEnrollmentKeysMap() (map[string]*models.EnrollmentKey, error) {
+	records, err := database.FetchRecords(database.ENROLLMENT_KEYS_TABLE_NAME)
+	if err != nil {
+		if !database.IsEmptyRecord(err) {
+			return nil, err
+		}
+	}
+	if records == nil {
+		records = make(map[string]string)
+	}
+	currentKeys := make(map[string]*models.EnrollmentKey, 0)
+	if len(records) > 0 {
+		for k := range records {
+			var currentKey models.EnrollmentKey
+			if err = json.Unmarshal([]byte(records[k]), &currentKey); err != nil {
+				continue
+			}
+			currentKeys[k] = &currentKey
+		}
+	}
+	return currentKeys, nil
+}

+ 206 - 0
logic/enrollmentkey_test.go

@@ -0,0 +1,206 @@
+package logic
+
+import (
+	"testing"
+	"time"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCreateEnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	t.Run("Can_Not_Create_Key", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, false)
+		assert.Nil(t, newKey)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.InvalidCreate)
+	})
+	t.Run("Can_Create_Key_Uses", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, false)
+		assert.Nil(t, err)
+		assert.Equal(t, 1, newKey.UsesRemaining)
+		assert.True(t, newKey.IsValid())
+	})
+	t.Run("Can_Create_Key_Time", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, false)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+	})
+	t.Run("Can_Create_Key_Unlimited", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, true)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+	})
+	t.Run("Can_Create_Key_WithNetworks", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+		assert.True(t, len(newKey.Networks) == 2)
+	})
+	t.Run("Can_Create_Key_WithTags", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, true)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+		assert.True(t, len(newKey.Tags) == 2)
+	})
+
+	t.Run("Can_Get_List_of_Keys", func(t *testing.T) {
+		keys, err := GetAllEnrollmentKeys()
+		assert.Nil(t, err)
+		assert.True(t, len(keys) > 0)
+		for i := range keys {
+			assert.Equal(t, len(keys[i].Value), models.EnrollmentKeyLength)
+		}
+	})
+	removeAllEnrollments()
+}
+
+func TestDelete_EnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+	t.Run("Can_Delete_Key", func(t *testing.T) {
+		assert.True(t, newKey.IsValid())
+		err := DeleteEnrollmentKey(newKey.Value)
+		assert.Nil(t, err)
+		oldKey, err := GetEnrollmentKey(newKey.Value)
+		assert.Nil(t, oldKey)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
+	})
+	t.Run("Can_Not_Delete_Invalid_Key", func(t *testing.T) {
+		err := DeleteEnrollmentKey("notakey")
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
+	})
+	removeAllEnrollments()
+}
+
+func TestDecrement_EnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, false)
+	t.Run("Check_initial_uses", func(t *testing.T) {
+		assert.True(t, newKey.IsValid())
+		assert.Equal(t, newKey.UsesRemaining, 1)
+	})
+	t.Run("Check can decrement", func(t *testing.T) {
+		assert.Equal(t, newKey.UsesRemaining, 1)
+		k, err := decrementEnrollmentKey(newKey.Value)
+		assert.Nil(t, err)
+		newKey = k
+	})
+	t.Run("Check can not decrement", func(t *testing.T) {
+		assert.Equal(t, newKey.UsesRemaining, 0)
+		_, err := decrementEnrollmentKey(newKey.Value)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoUsesRemaining)
+	})
+
+	removeAllEnrollments()
+}
+
+func TestUsability_EnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, false)
+	key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, false)
+	key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, true)
+	t.Run("Check if valid use key can be used", func(t *testing.T) {
+		assert.Equal(t, key1.UsesRemaining, 1)
+		ok := TryToUseEnrollmentKey(key1)
+		assert.True(t, ok)
+		assert.Equal(t, 0, key1.UsesRemaining)
+	})
+
+	t.Run("Check if valid time key can be used", func(t *testing.T) {
+		assert.True(t, !key2.Expiration.IsZero())
+		ok := TryToUseEnrollmentKey(key2)
+		assert.True(t, ok)
+	})
+
+	t.Run("Check if valid unlimited key can be used", func(t *testing.T) {
+		assert.True(t, key3.Unlimited)
+		ok := TryToUseEnrollmentKey(key3)
+		assert.True(t, ok)
+	})
+
+	t.Run("check invalid key can not be used", func(t *testing.T) {
+		ok := TryToUseEnrollmentKey(key1)
+		assert.False(t, ok)
+	})
+}
+
+func removeAllEnrollments() {
+	database.DeleteAllRecords(database.ENROLLMENT_KEYS_TABLE_NAME)
+}
+
+//Test that cheks if it can tokenize
+//Test that cheks if it can't tokenize
+
+func TestTokenize_EnrollmentKeys(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+	const defaultValue = "MwE5MwE5MwE5MwE5MwE5MwE5MwE5MwE5"
+	const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
+	const serverAddr = "api.myserver.com"
+	t.Run("Can_Not_Tokenize_Nil_Key", func(t *testing.T) {
+		err := Tokenize(nil, "ServerAddress")
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.FailedToTokenize)
+	})
+	t.Run("Can_Not_Tokenize_Empty_Server_Address", func(t *testing.T) {
+		err := Tokenize(newKey, "")
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.FailedToTokenize)
+	})
+
+	t.Run("Can_Tokenize", func(t *testing.T) {
+		err := Tokenize(newKey, serverAddr)
+		assert.Nil(t, err)
+		assert.True(t, len(newKey.Token) > 0)
+	})
+
+	t.Run("Is_Correct_B64_Token", func(t *testing.T) {
+		newKey.Value = defaultValue
+		err := Tokenize(newKey, serverAddr)
+		assert.Nil(t, err)
+		assert.Equal(t, newKey.Token, b64value)
+	})
+	removeAllEnrollments()
+}
+
+func TestDeTokenize_EnrollmentKeys(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+	const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
+	const serverAddr = "api.myserver.com"
+
+	t.Run("Can_Not_DeTokenize", func(t *testing.T) {
+		value, err := DeTokenize("")
+		assert.Nil(t, value)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.FailedToDeTokenize)
+	})
+	t.Run("Can_Not_Find_Key", func(t *testing.T) {
+		value, err := DeTokenize(b64Value)
+		assert.Nil(t, value)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
+	})
+	t.Run("Can_DeTokenize", func(t *testing.T) {
+		err := Tokenize(newKey, serverAddr)
+		assert.Nil(t, err)
+		output, err := DeTokenize(newKey.Token)
+		assert.Nil(t, err)
+		assert.NotNil(t, output)
+		assert.Equal(t, newKey.Value, output.Value)
+	})
+
+	removeAllEnrollments()
+}

+ 12 - 0
logic/host_test.go

@@ -1,6 +1,8 @@
 package logic
 package logic
 
 
 import (
 import (
+	"context"
+	"fmt"
 	"net"
 	"net"
 	"testing"
 	"testing"
 
 
@@ -12,6 +14,16 @@ import (
 
 
 func TestCheckPorts(t *testing.T) {
 func TestCheckPorts(t *testing.T) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
+	defer database.CloseDB()
+	peerUpdate := make(chan *models.Node)
+	go ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for y := range peerUpdate {
+			fmt.Printf("Pointless %v\n", y)
+			//do nothing
+		}
+	}()
+
 	h := models.Host{
 	h := models.Host{
 		ID:              uuid.New(),
 		ID:              uuid.New(),
 		EndpointIP:      net.ParseIP("192.168.1.1"),
 		EndpointIP:      net.ParseIP("192.168.1.1"),

+ 38 - 0
logic/hostactions/hostactions.go

@@ -0,0 +1,38 @@
+package hostactions
+
+import (
+	"sync"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// nodeActionHandler - handles the storage of host action updates
+var nodeActionHandler sync.Map
+
+// AddAction - adds a host action to a host's list to be retrieved from broker update
+func AddAction(hu models.HostUpdate) {
+	currentRecords, ok := nodeActionHandler.Load(hu.Host.ID.String())
+	if !ok { // no list exists yet
+		nodeActionHandler.Store(hu.Host.ID.String(), []models.HostUpdate{hu})
+	} else { // list exists, append to it
+		currentList := currentRecords.([]models.HostUpdate)
+		currentList = append(currentList, hu)
+		nodeActionHandler.Store(hu.Host.ID.String(), currentList)
+	}
+}
+
+// GetAction - gets an action if exists
+// TODO: may need to move to DB rather than sync map for HA
+func GetAction(id string) *models.HostUpdate {
+	currentRecords, ok := nodeActionHandler.Load(id)
+	if !ok {
+		return nil
+	}
+	currentList := currentRecords.([]models.HostUpdate)
+	if len(currentList) > 0 {
+		hu := currentList[0]
+		nodeActionHandler.Store(hu.Host.ID.String(), currentList[1:])
+		return &hu
+	}
+	return nil
+}

+ 9 - 17
logic/hosts.go

@@ -90,12 +90,13 @@ func CreateHost(h *models.Host) error {
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 		return ErrHostExists
 		return ErrHostExists
 	}
 	}
-	//encrypt that password so we never see it
+	// encrypt that password so we never see it
 	hash, err := bcrypt.GenerateFromPassword([]byte(h.HostPass), 5)
 	hash, err := bcrypt.GenerateFromPassword([]byte(h.HostPass), 5)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	h.HostPass = string(hash)
 	h.HostPass = string(hash)
+	checkForZombieHosts(h)
 	return UpsertHost(h)
 	return UpsertHost(h)
 }
 }
 
 
@@ -216,6 +217,7 @@ func UpdateHostNetwork(h *models.Host, network string, add bool) (*models.Node,
 		newNode := models.Node{}
 		newNode := models.Node{}
 		newNode.Server = servercfg.GetServer()
 		newNode.Server = servercfg.GetServer()
 		newNode.Network = network
 		newNode.Network = network
+		newNode.HostID = h.ID
 		if err := AssociateNodeToHost(&newNode, h); err != nil {
 		if err := AssociateNodeToHost(&newNode, h); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -234,7 +236,12 @@ func AssociateNodeToHost(n *models.Node, h *models.Host) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	h.Nodes = append(h.Nodes, n.ID.String())
+	currentHost, err := GetHost(h.ID.String())
+	if err != nil {
+		return err
+	}
+	h.HostPass = currentHost.HostPass
+	h.Nodes = append(currentHost.Nodes, n.ID.String())
 	return UpsertHost(h)
 	return UpsertHost(h)
 }
 }
 
 
@@ -308,21 +315,6 @@ func GetDefaultHosts() []models.Host {
 	return defaultHostList[:]
 	return defaultHostList[:]
 }
 }
 
 
-// AddDefaultHostsToNetwork - adds a node to network for every default host on Netmaker server
-func AddDefaultHostsToNetwork(network, server string) error {
-	// add default hosts to network
-	defaultHosts := GetDefaultHosts()
-	for i := range defaultHosts {
-		newNode := models.Node{}
-		newNode.Network = network
-		newNode.Server = server
-		if err := AssociateNodeToHost(&newNode, &defaultHosts[i]); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 // GetHostNetworks - fetches all the networks
 // GetHostNetworks - fetches all the networks
 func GetHostNetworks(hostID string) []string {
 func GetHostNetworks(hostID string) []string {
 	currHost, err := GetHost(hostID)
 	currHost, err := GetHost(hostID)

+ 0 - 37
logic/metrics/metrics.go

@@ -4,7 +4,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/logic"
 	proxy_metrics "github.com/gravitl/netmaker/metrics"
 	proxy_metrics "github.com/gravitl/netmaker/metrics"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl"
@@ -71,42 +70,6 @@ func Collect(iface, server, network string, peerMap models.PeerMap) (*models.Met
 	return &metrics, nil
 	return &metrics, nil
 }
 }
 
 
-// GetExchangedBytesForNode - get exchanged bytes for current node peers
-func GetExchangedBytesForNode(node *models.Node, metrics *models.Metrics) error {
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return err
-	}
-	peers, err := logic.GetPeerUpdate(node, host)
-	if err != nil {
-		logger.Log(0, "Failed to get peers: ", err.Error())
-		return err
-	}
-	wgclient, err := wgctrl.New()
-	if err != nil {
-		return err
-	}
-	defer wgclient.Close()
-	device, err := wgclient.Device(models.WIREGUARD_INTERFACE)
-	if err != nil {
-		return err
-	}
-	for _, currPeer := range device.Peers {
-		id := peers.PeerIDs[currPeer.PublicKey.String()].ID
-		address := peers.PeerIDs[currPeer.PublicKey.String()].Address
-		if id == "" || address == "" {
-			logger.Log(0, "attempted to parse metrics for invalid peer from server", id, address)
-			continue
-		}
-		logger.Log(2, "collecting exchanged bytes info for peer: ", address)
-		peerMetric := metrics.Connectivity[id]
-		peerMetric.TotalReceived = currPeer.ReceiveBytes
-		peerMetric.TotalSent = currPeer.TransmitBytes
-		metrics.Connectivity[id] = peerMetric
-	}
-	return nil
-}
-
 // == used to fill zero value data for non connected peers ==
 // == used to fill zero value data for non connected peers ==
 func fillUnconnectedData(metrics *models.Metrics, peerMap models.PeerMap) {
 func fillUnconnectedData(metrics *models.Metrics, peerMap models.PeerMap) {
 	for r := range peerMap {
 	for r := range peerMap {

+ 1 - 47
logic/nodes.go

@@ -1,7 +1,6 @@
 package logic
 package logic
 
 
 import (
 import (
-	"context"
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
@@ -382,22 +381,6 @@ func FindRelay(node *models.Node) *models.Node {
 	return nil
 	return nil
 }
 }
 
 
-func findNode(ip string) (*models.Node, error) {
-	nodes, err := GetAllNodes()
-	if err != nil {
-		return nil, err
-	}
-	for _, node := range nodes {
-		if node.Address.IP.String() == ip {
-			return &node, nil
-		}
-		if node.Address6.IP.String() == ip {
-			return &node, nil
-		}
-	}
-	return nil, errors.New("node not found")
-}
-
 // GetNetworkIngresses - gets the gateways of a network
 // GetNetworkIngresses - gets the gateways of a network
 func GetNetworkIngresses(network string) ([]models.Node, error) {
 func GetNetworkIngresses(network string) ([]models.Node, error) {
 	var ingresses []models.Node
 	var ingresses []models.Node
@@ -437,35 +420,6 @@ func updateProNodeACLS(node *models.Node) error {
 	return nil
 	return nil
 }
 }
 
 
-func PurgePendingNodes(ctx context.Context) {
-	ticker := time.NewTicker(NodePurgeCheckTime)
-	defer ticker.Stop()
-	for {
-		select {
-		case <-ctx.Done():
-			return
-		case <-ticker.C:
-			nodes, err := GetAllNodes()
-			if err != nil {
-				logger.Log(0, "PurgePendingNodes failed to retrieve nodes", err.Error())
-				continue
-			}
-			for _, node := range nodes {
-				if node.PendingDelete {
-					modified := node.LastModified
-					if time.Since(modified) > NodePurgeTime {
-						if err := DeleteNode(&node, true); err != nil {
-							logger.Log(0, "failed to purge node", node.ID.String(), err.Error())
-						} else {
-							logger.Log(0, "purged node ", node.ID.String())
-						}
-					}
-				}
-			}
-		}
-	}
-}
-
 // createNode - creates a node in database
 // createNode - creates a node in database
 func createNode(node *models.Node) error {
 func createNode(node *models.Node) error {
 	host, err := GetHost(node.HostID.String())
 	host, err := GetHost(node.HostID.String())
@@ -534,7 +488,7 @@ func createNode(node *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	CheckZombies(node, host.MacAddress)
+	CheckZombies(node)
 
 
 	nodebytes, err := json.Marshal(&node)
 	nodebytes, err := json.Marshal(&node)
 	if err != nil {
 	if err != nil {

+ 96 - 649
logic/peers.go

@@ -6,192 +6,16 @@ import (
 	"log"
 	"log"
 	"net"
 	"net"
 	"net/netip"
 	"net/netip"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slices"
 	"golang.org/x/exp/slices"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
-// GetPeersforProxy calculates the peers for a proxy
-// TODO ==========================
-// TODO ==========================
-// TODO ==========================
-// TODO ==========================
-// TODO ==========================
-// revisit this logic with new host/node models.
-func GetPeersForProxy(node *models.Node, onlyPeers bool) (models.ProxyManagerPayload, error) {
-	proxyPayload := models.ProxyManagerPayload{}
-	var peers []wgtypes.PeerConfig
-	peerConfMap := make(map[string]models.PeerConf)
-	var err error
-	currentPeers, err := GetNetworkNodes(node.Network)
-	if err != nil {
-		return proxyPayload, err
-	}
-	if !onlyPeers {
-		if node.IsRelayed {
-			relayNode := FindRelay(node)
-			relayHost, err := GetHost(relayNode.HostID.String())
-			if err != nil {
-				return proxyPayload, err
-			}
-			if relayNode != nil {
-				host, err := GetHost(relayNode.HostID.String())
-				if err != nil {
-					logger.Log(0, "error retrieving host for relay node", relayNode.HostID.String(), err.Error())
-				}
-				relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, host.ListenPort))
-				if err != nil {
-					logger.Log(1, "failed to resolve relay node endpoint: ", err.Error())
-				}
-				proxyPayload.IsRelayed = true
-				proxyPayload.RelayedTo = relayEndpoint
-			} else {
-				logger.Log(0, "couldn't find relay node for:  ", node.ID.String())
-			}
-
-		}
-		if node.IsRelay {
-			host, err := GetHost(node.HostID.String())
-			if err != nil {
-				logger.Log(0, "error retrieving host for relay node", node.ID.String(), err.Error())
-			}
-			relayedNodes, err := GetRelayedNodes(node)
-			if err != nil {
-				logger.Log(1, "failed to relayed nodes: ", node.ID.String(), err.Error())
-				proxyPayload.IsRelay = false
-			} else {
-				relayPeersMap := make(map[string]models.RelayedConf)
-				for _, relayedNode := range relayedNodes {
-					relayedNode := relayedNode
-					payload, err := GetPeersForProxy(&relayedNode, true)
-					if err == nil {
-						relayedHost, err := GetHost(relayedNode.HostID.String())
-						if err != nil {
-							logger.Log(0, "error retrieving host for relayNode", relayedNode.ID.String(), err.Error())
-						}
-						relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, host.ListenPort))
-						if udpErr == nil {
-							relayPeersMap[host.PublicKey.String()] = models.RelayedConf{
-								RelayedPeerEndpoint: relayedEndpoint,
-								RelayedPeerPubKey:   relayedHost.PublicKey.String(),
-								Peers:               payload.Peers,
-							}
-						}
-
-					}
-				}
-				proxyPayload.IsRelay = true
-				proxyPayload.RelayedPeerConf = relayPeersMap
-			}
-		}
-
-	}
-
-	for _, peer := range currentPeers {
-		if peer.ID == node.ID {
-			//skip yourself
-			continue
-		}
-		host, err := GetHost(peer.HostID.String())
-		if err != nil {
-			continue
-		}
-		proxyStatus := host.ProxyEnabled
-		listenPort := host.ListenPort
-		if proxyStatus {
-			listenPort = host.ProxyListenPort
-			if listenPort == 0 {
-				listenPort = models.NmProxyPort
-			}
-		} else if listenPort == 0 {
-			listenPort = host.ListenPort
-
-		}
-
-		endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", host.EndpointIP, listenPort))
-		if err != nil {
-			logger.Log(1, "failed to resolve udp addr for node: ", peer.ID.String(), host.EndpointIP.String(), err.Error())
-			continue
-		}
-		allowedips := GetAllowedIPs(node, &peer, nil)
-		var keepalive time.Duration
-		if node.PersistentKeepalive != 0 {
-			// set_keepalive
-			keepalive = node.PersistentKeepalive
-		}
-		peers = append(peers, wgtypes.PeerConfig{
-			PublicKey:                   host.PublicKey,
-			Endpoint:                    endpoint,
-			AllowedIPs:                  allowedips,
-			PersistentKeepaliveInterval: &keepalive,
-			ReplaceAllowedIPs:           true,
-		})
-		peerConfMap[host.PublicKey.String()] = models.PeerConf{
-			Address:          net.ParseIP(peer.PrimaryAddress()),
-			Proxy:            proxyStatus,
-			PublicListenPort: int32(listenPort),
-		}
-
-		if !onlyPeers && peer.IsRelayed {
-			relayNode := FindRelay(&peer)
-			if relayNode != nil {
-				relayHost, err := GetHost(relayNode.HostID.String())
-				if err != nil {
-					logger.Log(0, "error retrieving host for relayNode", relayNode.ID.String(), err.Error())
-					continue
-				}
-				relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, relayHost.ListenPort))
-				if err == nil {
-					peerConfMap[host.PublicKey.String()] = models.PeerConf{
-
-						IsRelayed:        true,
-						RelayedTo:        relayTo,
-						Address:          net.ParseIP(peer.PrimaryAddress()),
-						Proxy:            proxyStatus,
-						PublicListenPort: int32(listenPort),
-					}
-				}
-
-			}
-
-		}
-	}
-	if node.IsIngressGateway {
-		var extPeers []wgtypes.PeerConfig
-		extPeers, peerConfMap, err = getExtPeersForProxy(node, peerConfMap)
-		if err == nil {
-			peers = append(peers, extPeers...)
-
-		} else if !database.IsEmptyRecord(err) {
-			logger.Log(1, "error retrieving external clients:", err.Error())
-		}
-	}
-
-	proxyPayload.IsIngress = node.IsIngressGateway
-	addr := node.Address
-	if addr.String() == "" {
-		addr = node.Address6
-	}
-	proxyPayload.Peers = peers
-	proxyPayload.PeerMap = peerConfMap
-	//proxyPayload.Network = node.Network
-	//proxyPayload.InterfaceName = node.Interface
-	//hardcode or read from host ??
-	proxyPayload.InterfaceName = models.WIREGUARD_INTERFACE
-
-	return proxyPayload, nil
-}
-
 // GetProxyUpdateForHost - gets the proxy update for host
 // GetProxyUpdateForHost - gets the proxy update for host
 func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error) {
 func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error) {
 	proxyPayload := models.ProxyManagerPayload{
 	proxyPayload := models.ProxyManagerPayload{
@@ -201,7 +25,7 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 	if host.IsRelayed {
 	if host.IsRelayed {
 		relayHost, err := GetHost(host.RelayedBy)
 		relayHost, err := GetHost(host.RelayedBy)
 		if err == nil {
 		if err == nil {
-			relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, getPeerListenPort(relayHost)))
+			relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
 			if err != nil {
 			if err != nil {
 				logger.Log(1, "failed to resolve relay node endpoint: ", err.Error())
 				logger.Log(1, "failed to resolve relay node endpoint: ", err.Error())
 			}
 			}
@@ -210,16 +34,15 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 		} else {
 		} else {
 			logger.Log(0, "couldn't find relay host for:  ", host.ID.String())
 			logger.Log(0, "couldn't find relay host for:  ", host.ID.String())
 		}
 		}
-
 	}
 	}
 	if host.IsRelay {
 	if host.IsRelay {
 		relayedHosts := GetRelayedHosts(host)
 		relayedHosts := GetRelayedHosts(host)
 		relayPeersMap := make(map[string]models.RelayedConf)
 		relayPeersMap := make(map[string]models.RelayedConf)
 		for _, relayedHost := range relayedHosts {
 		for _, relayedHost := range relayedHosts {
 			relayedHost := relayedHost
 			relayedHost := relayedHost
-			payload, err := GetPeerUpdateForHost(&relayedHost)
+			payload, err := GetPeerUpdateForHost("", &relayedHost, nil)
 			if err == nil {
 			if err == nil {
-				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, getPeerListenPort(&relayedHost)))
+				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
 				if udpErr == nil {
 				if udpErr == nil {
 					relayPeersMap[relayedHost.PublicKey.String()] = models.RelayedConf{
 					relayPeersMap[relayedHost.PublicKey.String()] = models.RelayedConf{
 						RelayedPeerEndpoint: relayedEndpoint,
 						RelayedPeerEndpoint: relayedEndpoint,
@@ -259,14 +82,14 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 			if currPeerConf, found = peerConfMap[peerHost.PublicKey.String()]; !found {
 			if currPeerConf, found = peerConfMap[peerHost.PublicKey.String()]; !found {
 				currPeerConf = models.PeerConf{
 				currPeerConf = models.PeerConf{
 					Proxy:            peerHost.ProxyEnabled,
 					Proxy:            peerHost.ProxyEnabled,
-					PublicListenPort: int32(getPeerListenPort(peerHost)),
+					PublicListenPort: int32(GetPeerListenPort(peerHost)),
 				}
 				}
 			}
 			}
 
 
 			if peerHost.IsRelayed && peerHost.RelayedBy != host.ID.String() {
 			if peerHost.IsRelayed && peerHost.RelayedBy != host.ID.String() {
 				relayHost, err := GetHost(peerHost.RelayedBy)
 				relayHost, err := GetHost(peerHost.RelayedBy)
 				if err == nil {
 				if err == nil {
-					relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, getPeerListenPort(relayHost)))
+					relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
 					if err == nil {
 					if err == nil {
 						currPeerConf.IsRelayed = true
 						currPeerConf.IsRelayed = true
 						currPeerConf.RelayedTo = relayTo
 						currPeerConf.RelayedTo = relayTo
@@ -294,36 +117,41 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 }
 }
 
 
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
-func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
+func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
 	if host == nil {
 	if host == nil {
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 	}
 	}
+	// track which nodes are deleted
+	// after peer calculation, if peer not in list, add delete config of peer
 	hostPeerUpdate := models.HostPeerUpdate{
 	hostPeerUpdate := models.HostPeerUpdate{
 		Host:          *host,
 		Host:          *host,
 		Server:        servercfg.GetServer(),
 		Server:        servercfg.GetServer(),
-		Network:       make(map[string]models.NetworkInfo),
-		PeerIDs:       make(models.HostPeerMap),
+		HostPeerIDs:   make(models.HostPeerMap, 0),
 		ServerVersion: servercfg.GetVersion(),
 		ServerVersion: servercfg.GetVersion(),
 		ServerAddrs:   []models.ServerAddr{},
 		ServerAddrs:   []models.ServerAddr{},
 		IngressInfo: models.IngressInfo{
 		IngressInfo: models.IngressInfo{
 			ExtPeers: make(map[string]models.ExtClientInfo),
 			ExtPeers: make(map[string]models.ExtClientInfo),
 		},
 		},
 		EgressInfo: make(map[string]models.EgressInfo),
 		EgressInfo: make(map[string]models.EgressInfo),
+		PeerIDs:    make(models.PeerMap, 0),
+		Peers:      []wgtypes.PeerConfig{},
+		NodePeers:  []wgtypes.PeerConfig{},
+	}
+	var deletedNodes = []models.Node{} // used to track deleted nodes
+	if deletedNode != nil {
+		deletedNodes = append(deletedNodes, *deletedNode)
 	}
 	}
-	logger.Log(1, "peer update for host ", host.ID.String())
+	logger.Log(1, "peer update for host", host.ID.String())
 	peerIndexMap := make(map[string]int)
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
 	for _, nodeID := range host.Nodes {
+		nodeID := nodeID
 		node, err := GetNodeByID(nodeID)
 		node, err := GetNodeByID(nodeID)
 		if err != nil {
 		if err != nil {
 			continue
 			continue
 		}
 		}
-		if !node.Connected || node.Action == models.NODE_DELETE || node.PendingDelete {
+		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
 			continue
 			continue
 		}
 		}
-
-		hostPeerUpdate.Network[node.Network] = models.NetworkInfo{
-			DNS: getPeerDNS(node.Network),
-		}
 		currentPeers, err := GetNetworkNodes(node.Network)
 		currentPeers, err := GetNetworkNodes(node.Network)
 		if err != nil {
 		if err != nil {
 			log.Println("no network nodes")
 			log.Println("no network nodes")
@@ -334,10 +162,14 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			nodePeerMap = make(map[string]models.PeerRouteInfo)
 			nodePeerMap = make(map[string]models.PeerRouteInfo)
 		}
 		}
 		for _, peer := range currentPeers {
 		for _, peer := range currentPeers {
-			if peer.ID == node.ID {
+			peer := peer
+			if peer.ID.String() == node.ID.String() {
 				logger.Log(2, "peer update, skipping self")
 				logger.Log(2, "peer update, skipping self")
 				//skip yourself
 				//skip yourself
-
+				continue
+			}
+			if peer.Action == models.NODE_DELETE || peer.PendingDelete {
+				deletedNodes = append(deletedNodes, peer) // track deleted node for peer update
 				continue
 				continue
 			}
 			}
 			var peerConfig wgtypes.PeerConfig
 			var peerConfig wgtypes.PeerConfig
@@ -347,13 +179,13 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 				return models.HostPeerUpdate{}, err
 				return models.HostPeerUpdate{}, err
 			}
 			}
 
 
-			if !peer.Connected || peer.Action == models.NODE_DELETE || peer.PendingDelete {
+			if !peer.Connected {
 				logger.Log(2, "peer update, skipping unconnected node", peer.ID.String())
 				logger.Log(2, "peer update, skipping unconnected node", peer.ID.String())
 				//skip unconnected nodes
 				//skip unconnected nodes
 				continue
 				continue
 			}
 			}
 			if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
 			if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
-				log.Println("peer update, skipping node for acl")
+				logger.Log(2, "peer update, skipping node for acl")
 				//skip if not permitted by acl
 				//skip if not permitted by acl
 				continue
 				continue
 			}
 			}
@@ -375,7 +207,7 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 			}
 			peerConfig.Endpoint = &net.UDPAddr{
 			peerConfig.Endpoint = &net.UDPAddr{
 				IP:   peerHost.EndpointIP,
 				IP:   peerHost.EndpointIP,
-				Port: getPeerListenPort(peerHost),
+				Port: GetPeerListenPort(peerHost),
 			}
 			}
 
 
 			if uselocal {
 			if uselocal {
@@ -395,7 +227,26 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 			}
 			peerConfig.AllowedIPs = allowedips
 			peerConfig.AllowedIPs = allowedips
 			if node.IsIngressGateway || node.IsEgressGateway {
 			if node.IsIngressGateway || node.IsEgressGateway {
-
+				if peer.IsIngressGateway {
+					_, extPeerIDAndAddrs, err := getExtPeers(&peer)
+					if err == nil {
+						for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+							extPeerIdAndAddr := extPeerIdAndAddr
+							nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
+								PeerAddr: net.IPNet{
+									IP:   net.ParseIP(extPeerIdAndAddr.Address),
+									Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+								},
+								PeerKey: extPeerIdAndAddr.ID,
+								Allow:   true,
+							}
+						}
+					}
+				}
+				if node.IsIngressGateway && peer.IsEgressGateway {
+					hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
+						peer.EgressGatewayRanges...)
+				}
 				nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
 				nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
 					PeerAddr: net.IPNet{
 					PeerAddr: net.IPNet{
 						IP:   net.ParseIP(peer.PrimaryAddress()),
 						IP:   net.ParseIP(peer.PrimaryAddress()),
@@ -406,28 +257,40 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 				}
 				}
 			}
 			}
 
 
-			if _, ok := hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()]; !ok {
-				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
+			var nodePeer wgtypes.PeerConfig
+			if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
+				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
 				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
 				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
 				peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
 				peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
-				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
+				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 					ID:      peer.ID.String(),
 					ID:      peer.ID.String(),
 					Address: peer.PrimaryAddress(),
 					Address: peer.PrimaryAddress(),
 					Name:    peerHost.Name,
 					Name:    peerHost.Name,
 					Network: peer.Network,
 					Network: peer.Network,
 				}
 				}
+				nodePeer = peerConfig
 			} else {
 			} else {
 				peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
 				peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
 				peerAllowedIPs = append(peerAllowedIPs, allowedips...)
 				peerAllowedIPs = append(peerAllowedIPs, allowedips...)
 				hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
 				hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
-				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
+				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 					ID:      peer.ID.String(),
 					ID:      peer.ID.String(),
 					Address: peer.PrimaryAddress(),
 					Address: peer.PrimaryAddress(),
 					Name:    peerHost.Name,
 					Name:    peerHost.Name,
 					Network: peer.Network,
 					Network: peer.Network,
 				}
 				}
+				nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
 			}
 			}
 
 
+			if node.Network == network { // add to peers map for metrics
+				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
+					ID:      peer.ID.String(),
+					Address: peer.PrimaryAddress(),
+					Name:    peerHost.Name,
+					Network: peer.Network,
+				}
+				hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
+			}
 		}
 		}
 		var extPeers []wgtypes.PeerConfig
 		var extPeers []wgtypes.PeerConfig
 		var extPeerIDAndAddrs []models.IDandAddr
 		var extPeerIDAndAddrs []models.IDandAddr
@@ -435,6 +298,7 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
 			extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
 			if err == nil {
 			if err == nil {
 				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
 				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+					extPeerIdAndAddr := extPeerIdAndAddr
 					nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
 					nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
 						PeerAddr: net.IPNet{
 						PeerAddr: net.IPNet{
 							IP:   net.ParseIP(extPeerIdAndAddr.Address),
 							IP:   net.ParseIP(extPeerIdAndAddr.Address),
@@ -446,8 +310,9 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 				}
 				}
 				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
 				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
 				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
 				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
-					hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
-					hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
+					extPeerIdAndAddr := extPeerIdAndAddr
+					hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
+					hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
 						ID:      extPeerIdAndAddr.ID,
 						ID:      extPeerIdAndAddr.ID,
 						Address: extPeerIdAndAddr.Address,
 						Address: extPeerIdAndAddr.Address,
 						Name:    extPeerIdAndAddr.Name,
 						Name:    extPeerIdAndAddr.Name,
@@ -467,8 +332,11 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 						ExtPeerKey: extPeerIdAndAddr.ID,
 						ExtPeerKey: extPeerIdAndAddr.ID,
 						Peers:      nodePeerMap,
 						Peers:      nodePeerMap,
 					}
 					}
+					if node.Network == network {
+						hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
+						hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, extPeers...)
+					}
 				}
 				}
-
 			} else if !database.IsEmptyRecord(err) {
 			} else if !database.IsEmptyRecord(err) {
 				logger.Log(1, "error retrieving external clients:", err.Error())
 				logger.Log(1, "error retrieving external clients:", err.Error())
 			}
 			}
@@ -487,10 +355,33 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 		}
 		}
 	}
 	}
 
 
+	// run through delete nodes
+	if len(deletedNodes) > 0 {
+		for i := range deletedNodes {
+			delNode := deletedNodes[i]
+			delHost, err := GetHost(delNode.HostID.String())
+			if err != nil {
+				continue
+			}
+			if _, ok := hostPeerUpdate.HostPeerIDs[delHost.PublicKey.String()]; !ok {
+				var peerConfig = wgtypes.PeerConfig{}
+				peerConfig.PublicKey = delHost.PublicKey
+				peerConfig.Endpoint = &net.UDPAddr{
+					IP:   delHost.EndpointIP,
+					Port: GetPeerListenPort(delHost),
+				}
+				peerConfig.Remove = true
+				peerConfig.AllowedIPs = []net.IPNet{delNode.Address, delNode.Address6}
+				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
+			}
+		}
+	}
+
 	return hostPeerUpdate, nil
 	return hostPeerUpdate, nil
 }
 }
 
 
-func getPeerListenPort(host *models.Host) int {
+// GetPeerListenPort - given a host, retrieve it's appropriate listening port
+func GetPeerListenPort(host *models.Host) int {
 	peerPort := host.ListenPort
 	peerPort := host.ListenPort
 	if host.ProxyEnabled {
 	if host.ProxyEnabled {
 		if host.PublicListenPort != 0 {
 		if host.PublicListenPort != 0 {
@@ -502,286 +393,6 @@ func getPeerListenPort(host *models.Host) int {
 	return peerPort
 	return peerPort
 }
 }
 
 
-// GetPeerUpdate - gets a wireguard peer config for each peer of a node
-func GetPeerUpdate(node *models.Node, host *models.Host) (models.PeerUpdate, error) {
-	log.Println("peer update for node ", node.ID)
-	peerUpdate := models.PeerUpdate{
-		Network:       node.Network,
-		ServerVersion: ncutils.Version,
-		DNS:           getPeerDNS(node.Network),
-		PeerIDs:       make(models.PeerMap),
-	}
-	currentPeers, err := GetNetworkNodes(node.Network)
-	if err != nil {
-		log.Println("no network nodes")
-		return models.PeerUpdate{}, err
-	}
-	for _, peer := range currentPeers {
-		var peerConfig wgtypes.PeerConfig
-		peerHost, err := GetHost(peer.HostID.String())
-		if err != nil {
-			log.Println("no peer host", err)
-			return models.PeerUpdate{}, err
-		}
-		if peer.ID == node.ID {
-			log.Println("peer update, skipping self")
-			//skip yourself
-
-			continue
-		}
-		if !peer.Connected {
-			log.Println("peer update, skipping unconnected node")
-			//skip unconnected nodes
-			continue
-		}
-		if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
-			log.Println("peer update, skipping node for acl")
-			//skip if not permitted by acl
-			continue
-		}
-		peerConfig.PublicKey = peerHost.PublicKey
-		peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive
-		peerConfig.ReplaceAllowedIPs = true
-		uselocal := false
-		if host.EndpointIP.String() == peerHost.EndpointIP.String() {
-			//peer is on same network
-			// set to localaddress
-			uselocal = true
-			if node.LocalAddress.IP == nil {
-				// use public endpint
-				uselocal = false
-			}
-			if node.LocalAddress.String() == peer.LocalAddress.String() {
-				uselocal = false
-			}
-		}
-		peerConfig.Endpoint = &net.UDPAddr{
-			IP:   peerHost.EndpointIP,
-			Port: peerHost.ListenPort,
-		}
-		if peerHost.ProxyEnabled {
-			peerConfig.Endpoint.Port = getPeerListenPort(peerHost)
-		}
-		if uselocal {
-			peerConfig.Endpoint.IP = peer.LocalAddress.IP
-		}
-		allowedips := getNodeAllowedIPs(&peer, node)
-		if peer.IsIngressGateway {
-			for _, entry := range peer.IngressGatewayRange {
-				_, cidr, err := net.ParseCIDR(string(entry))
-				if err == nil {
-					allowedips = append(allowedips, *cidr)
-				}
-			}
-		}
-		if peer.IsEgressGateway {
-			allowedips = append(allowedips, getEgressIPs(node, &peer)...)
-		}
-
-		peerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
-			ID:      peer.ID.String(),
-			Address: peer.PrimaryAddress(),
-			Name:    peerHost.Name,
-			Network: peer.Network,
-		}
-		peerConfig.AllowedIPs = allowedips
-		peerUpdate.Peers = append(peerUpdate.Peers, peerConfig)
-	}
-	return peerUpdate, nil
-}
-
-// func getRelayAllowedIPs(node, peer *models.Node) []net.IPNet {
-// 	var allowedips []net.IPNet
-// 	var allowedip net.IPNet
-// 	for _, addr := range peer.RelayAddrs {
-// 		if node.Address.IP.String() == addr {
-// 			continue
-// 		}
-// 		if node.Address6.IP.String() == addr {
-// 			continue
-// 		}
-// 		allowedip.IP = net.ParseIP(addr)
-// 		allowedips = append(allowedips, allowedip)
-// 	}
-// 	return allowedips
-// }
-
-// GetPeerUpdateLegacy - gets a wireguard peer config for each peer of a node
-func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
-	var peerUpdate models.PeerUpdate
-	var peers []wgtypes.PeerConfig
-	var serverNodeAddresses = []models.ServerAddr{}
-	var peerMap = make(models.PeerMap)
-	var metrics *models.Metrics
-	if servercfg.Is_EE {
-		metrics, _ = GetMetrics(node.ID.String())
-	}
-	if metrics == nil {
-		metrics = &models.Metrics{}
-	}
-	if metrics.FailoverPeers == nil {
-		metrics.FailoverPeers = make(map[string]string)
-	}
-	// udppeers = the peers parsed from the local interface
-	// gives us correct port to reach
-	udppeers, errN := database.GetPeers(node.Network)
-	if errN != nil {
-		logger.Log(2, errN.Error())
-	}
-
-	currentPeers, err := GetNetworkNodes(node.Network)
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	host, err := GetHost(node.HostID.String())
-	if err != nil {
-		return peerUpdate, err
-	}
-	if node.IsRelayed && !host.ProxyEnabled {
-		return GetPeerUpdateForRelayedNode(node, udppeers)
-	}
-
-	// #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
-	for _, peer := range currentPeers {
-		peerHost, err := GetHost(peer.HostID.String())
-		if err != nil {
-			logger.Log(0, "error retrieving host for peer", node.ID.String(), err.Error())
-			return models.PeerUpdate{}, err
-		}
-		if peer.ID == node.ID {
-			//skip yourself
-			continue
-		}
-		if node.Connected {
-			//skip unconnected nodes
-			continue
-		}
-
-		// if the node is not a server, set the endpoint
-		var setEndpoint = true
-
-		if peer.IsRelayed {
-			if !peerHost.ProxyEnabled && !(node.IsRelay && ncutils.StringSliceContains(node.RelayAddrs, peer.PrimaryAddress())) {
-				//skip -- will be added to relay
-				continue
-			} else if node.IsRelay && ncutils.StringSliceContains(node.RelayAddrs, peer.PrimaryAddress()) {
-				// dont set peer endpoint if it's relayed by node
-				setEndpoint = false
-			}
-		}
-		if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
-			//skip if not permitted by acl
-			continue
-		}
-		if len(metrics.FailoverPeers[peer.ID.String()]) > 0 && IsFailoverPresent(node.Network) {
-			logger.Log(2, "peer", peer.ID.String(), peer.PrimaryAddress(), "was found to be in failover peers list for node", node.ID.String(), node.PrimaryAddress())
-			continue
-		}
-		if err != nil {
-			return models.PeerUpdate{}, err
-		}
-		host, err := GetHost(node.HostID.String())
-		if err != nil {
-			logger.Log(0, "error retrieving host for node", node.ID.String(), err.Error())
-			return models.PeerUpdate{}, err
-		}
-		if host.EndpointIP.String() == peerHost.EndpointIP.String() {
-			//peer is on same network
-			// set_local
-			if node.LocalAddress.String() != peer.LocalAddress.String() && peer.LocalAddress.IP != nil {
-				peerHost.EndpointIP = peer.LocalAddress.IP
-				if peerHost.ListenPort != 0 {
-					peerHost.ListenPort = getPeerListenPort(peerHost)
-				}
-			} else {
-				continue
-			}
-		}
-
-		// set address if setEndpoint is true
-		// otherwise, will get inserted as empty value
-		var address *net.UDPAddr
-
-		// Sets ListenPort to UDP Hole Punching Port assuming:
-		// - UDP Hole Punching is enabled
-		// - udppeers retrieval did not return an error
-		// - the endpoint is valid
-		if setEndpoint {
-
-			var setUDPPort = false
-			if CheckEndpoint(udppeers[peerHost.PublicKey.String()]) {
-				endpointstring := udppeers[peerHost.PublicKey.String()]
-				endpointarr := strings.Split(endpointstring, ":")
-				if len(endpointarr) == 2 {
-					port, err := strconv.Atoi(endpointarr[1])
-					if err == nil {
-						setUDPPort = true
-						peerHost.ListenPort = port
-					}
-				}
-			}
-			// if udp hole punching is on, but udp hole punching did not set it, use the LocalListenPort instead
-			// or, if port is for some reason zero use the LocalListenPort
-			// but only do this if LocalListenPort is not zero
-			if ((!setUDPPort) || peerHost.ListenPort == 0) && peerHost.ListenPort != 0 {
-				peerHost.ListenPort = getPeerListenPort(peerHost)
-			}
-
-			endpoint := peerHost.EndpointIP.String() + ":" + strconv.FormatInt(int64(peerHost.ListenPort), 10)
-			address, err = net.ResolveUDPAddr("udp", endpoint)
-			if err != nil {
-				return models.PeerUpdate{}, err
-			}
-		}
-		allowedips := GetAllowedIPs(node, &peer, metrics)
-		var keepalive time.Duration
-		if node.PersistentKeepalive != 0 {
-			// set_keepalive
-			keepalive = node.PersistentKeepalive
-		}
-		var peerData = wgtypes.PeerConfig{
-			PublicKey:                   peerHost.PublicKey,
-			Endpoint:                    address,
-			ReplaceAllowedIPs:           true,
-			AllowedIPs:                  allowedips,
-			PersistentKeepaliveInterval: &keepalive,
-		}
-
-		peers = append(peers, peerData)
-		peerMap[peerHost.PublicKey.String()] = models.IDandAddr{
-			Name:     peerHost.Name,
-			ID:       peer.ID.String(),
-			Address:  peer.PrimaryAddress(),
-			IsServer: "no",
-		}
-
-	}
-	if node.IsIngressGateway {
-		extPeers, idsAndAddr, err := getExtPeers(node)
-		if err == nil {
-			peers = append(peers, extPeers...)
-			for i := range idsAndAddr {
-				peerMap[idsAndAddr[i].ID] = idsAndAddr[i]
-			}
-		} else if !database.IsEmptyRecord(err) {
-			logger.Log(1, "error retrieving external clients:", err.Error())
-		}
-	}
-
-	peerUpdate.Network = node.Network
-	peerUpdate.ServerVersion = servercfg.Version
-	sort.SliceStable(peers[:], func(i, j int) bool {
-		return peers[i].PublicKey.String() < peers[j].PublicKey.String()
-	})
-	peerUpdate.Peers = peers
-	peerUpdate.ServerAddrs = serverNodeAddresses
-	peerUpdate.DNS = getPeerDNS(node.Network)
-	peerUpdate.PeerIDs = peerMap
-	return peerUpdate, nil
-}
-
 func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, error) {
 func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, error) {
 	var peers []wgtypes.PeerConfig
 	var peers []wgtypes.PeerConfig
 	var idsAndAddr []models.IDandAddr
 	var idsAndAddr []models.IDandAddr
@@ -948,170 +559,6 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet
 	return allowedips
 	return allowedips
 }
 }
 
 
-func getPeerDNS(network string) string {
-	var dns string
-	if nodes, err := GetNetworkNodes(network); err == nil {
-		for i, node := range nodes {
-			host, err := GetHost(node.HostID.String())
-			if err != nil {
-				logger.Log(0, "error retrieving host for node", node.ID.String(), err.Error())
-				continue
-			}
-			dns = dns + fmt.Sprintf("%s %s.%s\n", nodes[i].Address, host.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
-}
-
-// GetPeerUpdateForRelayedNode - calculates peer update for a relayed node by getting the relay
-// copying the relay node's allowed ips and making appropriate substitutions
-func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string) (models.PeerUpdate, error) {
-	var peerUpdate models.PeerUpdate
-	var peers []wgtypes.PeerConfig
-	var serverNodeAddresses = []models.ServerAddr{}
-	var allowedips []net.IPNet
-	//find node that is relaying us
-	relay := FindRelay(node)
-	if relay == nil {
-		return models.PeerUpdate{}, errors.New("not found")
-	}
-
-	//add relay to lists of allowed ip
-	if relay.Address.IP != nil {
-		relayIP := relay.Address
-		allowedips = append(allowedips, relayIP)
-	}
-	if relay.Address6.IP != nil {
-		relayIP6 := relay.Address6
-		allowedips = append(allowedips, relayIP6)
-	}
-	//get PeerUpdate for relayed node
-	relayHost, err := GetHost(relay.HostID.String())
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	relayPeerUpdate, err := GetPeerUpdate(relay, relayHost)
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	//add the relays allowed ips from all of the relay's peers
-	for _, peer := range relayPeerUpdate.Peers {
-		allowedips = append(allowedips, peer.AllowedIPs...)
-	}
-	//delete any ips not permitted by acl
-	for i := len(allowedips) - 1; i >= 0; i-- {
-		target, err := findNode(allowedips[i].IP.String())
-		if err != nil {
-			logger.Log(0, "failed to find node for ip", allowedips[i].IP.String(), err.Error())
-			continue
-		}
-		if target == nil {
-			logger.Log(0, "failed to find node for ip", allowedips[i].IP.String())
-			continue
-		}
-		if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(target.ID.String())) {
-			logger.Log(0, "deleting node from relayednode per acl", node.ID.String(), target.ID.String())
-			allowedips = append(allowedips[:i], allowedips[i+1:]...)
-		}
-	}
-	//delete self from allowed ips
-	for i := len(allowedips) - 1; i >= 0; i-- {
-		if allowedips[i].IP.String() == node.Address.IP.String() || allowedips[i].IP.String() == node.Address6.IP.String() {
-			allowedips = append(allowedips[:i], allowedips[i+1:]...)
-		}
-	}
-	//delete egressrange from allowedip if we are egress gateway
-	if node.IsEgressGateway {
-		for i := len(allowedips) - 1; i >= 0; i-- {
-			if StringSliceContains(node.EgressGatewayRanges, allowedips[i].String()) {
-				allowedips = append(allowedips[:i], allowedips[i+1:]...)
-			}
-		}
-	}
-	//delete extclients from allowedip if we are ingress gateway
-	if node.IsIngressGateway {
-		for i := len(allowedips) - 1; i >= 0; i-- {
-			if strings.Contains(node.IngressGatewayRange, allowedips[i].IP.String()) {
-				allowedips = append(allowedips[:i], allowedips[i+1:]...)
-			}
-		}
-	}
-	//add egress range if relay is egress
-	if relay.IsEgressGateway {
-		var ip *net.IPNet
-		for _, cidr := range relay.EgressGatewayRanges {
-			_, ip, err = net.ParseCIDR(cidr)
-			if err != nil {
-				continue
-			}
-		}
-		allowedips = append(allowedips, *ip)
-	}
-	var setUDPPort = false
-	var listenPort int
-	if CheckEndpoint(udppeers[relayHost.PublicKey.String()]) {
-		endpointstring := udppeers[relayHost.PublicKey.String()]
-		endpointarr := strings.Split(endpointstring, ":")
-		if len(endpointarr) == 2 {
-			port, err := strconv.Atoi(endpointarr[1])
-			if err == nil {
-				setUDPPort = true
-				listenPort = port
-			}
-		}
-	}
-	// if udp hole punching is on, but udp hole punching did not set it, use the LocalListenPort instead
-	// or, if port is for some reason zero use the LocalListenPort
-	// but only do this if LocalListenPort is not zero
-	if ((!setUDPPort) || relayHost.ListenPort == 0) && relayHost.ListenPort != 0 {
-		listenPort = relayHost.ListenPort
-	}
-
-	endpoint := relayHost.EndpointIP.String() + ":" + strconv.FormatInt(int64(listenPort), 10)
-	address, err := net.ResolveUDPAddr("udp", endpoint)
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	var keepalive time.Duration
-	if node.PersistentKeepalive != 0 {
-		// set_keepalive
-		keepalive = node.PersistentKeepalive
-	}
-	var peerData = wgtypes.PeerConfig{
-		PublicKey:                   relayHost.PublicKey,
-		Endpoint:                    address,
-		ReplaceAllowedIPs:           true,
-		AllowedIPs:                  allowedips,
-		PersistentKeepaliveInterval: &keepalive,
-	}
-	peers = append(peers, peerData)
-	//if ingress add extclients
-	if node.IsIngressGateway {
-		extPeers, _, err := getExtPeers(node)
-		if err == nil {
-			peers = append(peers, extPeers...)
-		} else {
-			logger.Log(2, "could not retrieve ext peers for ", node.ID.String(), err.Error())
-		}
-	}
-	peerUpdate.Network = node.Network
-	peerUpdate.ServerVersion = servercfg.Version
-	sort.SliceStable(peers[:], func(i, j int) bool {
-		return peers[i].PublicKey.String() < peers[j].PublicKey.String()
-	})
-	peerUpdate.Peers = peers
-	peerUpdate.ServerAddrs = serverNodeAddresses
-	peerUpdate.DNS = getPeerDNS(node.Network)
-	return peerUpdate, nil
-}
-
 func getEgressIPs(node, peer *models.Node) []net.IPNet {
 func getEgressIPs(node, peer *models.Node) []net.IPNet {
 	host, err := GetHost(node.HostID.String())
 	host, err := GetHost(node.HostID.String())
 	if err != nil {
 	if err != nil {

+ 5 - 1
logic/pro/networkuser_test.go

@@ -10,8 +10,12 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 )
 )
 
 
-func TestNetworkUserLogic(t *testing.T) {
+func TestMain(m *testing.M) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
+	defer database.CloseDB()
+}
+
+func TestNetworkUserLogic(t *testing.T) {
 	networkUser := promodels.NetworkUser{
 	networkUser := promodels.NetworkUser{
 		ID: "helloworld",
 		ID: "helloworld",
 	}
 	}

+ 0 - 2
logic/pro/usergroups_test.go

@@ -3,13 +3,11 @@ package pro
 import (
 import (
 	"testing"
 	"testing"
 
 
-	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 )
 )
 
 
 func TestUserGroupLogic(t *testing.T) {
 func TestUserGroupLogic(t *testing.T) {
-	database.InitializeDatabase()
 
 
 	t.Run("User Groups initialized successfully", func(t *testing.T) {
 	t.Run("User Groups initialized successfully", func(t *testing.T) {
 		err := InitializeGroups()
 		err := InitializeGroups()

+ 60 - 26
logic/zombie.go

@@ -2,7 +2,6 @@ package logic
 
 
 import (
 import (
 	"context"
 	"context"
-	"net"
 	"time"
 	"time"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
@@ -11,22 +10,23 @@ import (
 )
 )
 
 
 const (
 const (
-	// ZOMBIE_TIMEOUT - timeout in seconds for checking zombie status
-	ZOMBIE_TIMEOUT = 60
+	// ZOMBIE_TIMEOUT - timeout in hours for checking zombie status
+	ZOMBIE_TIMEOUT = 6
 	// ZOMBIE_DELETE_TIME - timeout in minutes for zombie node deletion
 	// ZOMBIE_DELETE_TIME - timeout in minutes for zombie node deletion
 	ZOMBIE_DELETE_TIME = 10
 	ZOMBIE_DELETE_TIME = 10
 )
 )
 
 
 var (
 var (
-	zombies      []uuid.UUID
-	removeZombie chan uuid.UUID = make(chan (uuid.UUID), 10)
-	newZombie    chan uuid.UUID = make(chan (uuid.UUID), 10)
+	zombies       []uuid.UUID
+	hostZombies   []uuid.UUID
+	newZombie     chan uuid.UUID = make(chan (uuid.UUID), 10)
+	newHostZombie chan uuid.UUID = make(chan (uuid.UUID), 10)
 )
 )
 
 
-// CheckZombies - checks if new node has same macaddress as existing node
+// CheckZombies - checks if new node has same hostid as existing node
 // if so, existing node is added to zombie node quarantine list
 // if so, existing node is added to zombie node quarantine list
 // also cleans up nodes past their expiration date
 // also cleans up nodes past their expiration date
-func CheckZombies(newnode *models.Node, mac net.HardwareAddr) {
+func CheckZombies(newnode *models.Node) {
 	nodes, err := GetNetworkNodes(newnode.Network)
 	nodes, err := GetNetworkNodes(newnode.Network)
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "Failed to retrieve network nodes", newnode.Network, err.Error())
 		logger.Log(1, "Failed to retrieve network nodes", newnode.Network, err.Error())
@@ -44,6 +44,35 @@ func CheckZombies(newnode *models.Node, mac net.HardwareAddr) {
 	}
 	}
 }
 }
 
 
+// checkForZombieHosts - checks if new host has the same macAddress as an existing host
+// if true, existing host is added to host zombie collection
+func checkForZombieHosts(h *models.Host) {
+	hosts, err := GetAllHosts()
+	if err != nil {
+		logger.Log(3, "errror retrieving all hosts", err.Error())
+	}
+	for _, existing := range hosts {
+		if existing.ID == h.ID {
+			//probably an unnecessary check as new host should not be in database yet, but just in case
+			//skip self
+			continue
+		}
+		if existing.MacAddress.String() == h.MacAddress.String() {
+			//add to hostZombies
+			newHostZombie <- existing.ID
+			//add all nodes belonging to host to zombile list
+			for _, node := range existing.Nodes {
+				id, err := uuid.Parse(node)
+				if err != nil {
+					logger.Log(3, "error parsing uuid from host.Nodes", err.Error())
+					continue
+				}
+				newHostZombie <- id
+			}
+		}
+	}
+}
+
 // ManageZombies - goroutine which adds/removes/deletes nodes from the zombie node quarantine list
 // ManageZombies - goroutine which adds/removes/deletes nodes from the zombie node quarantine list
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	logger.Log(2, "Zombie management started")
 	logger.Log(2, "Zombie management started")
@@ -51,25 +80,13 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	for {
 	for {
 		select {
 		select {
 		case <-ctx.Done():
 		case <-ctx.Done():
+			close(peerUpdate)
 			return
 			return
 		case id := <-newZombie:
 		case id := <-newZombie:
-			logger.Log(1, "adding", id.String(), "to zombie quaratine list")
 			zombies = append(zombies, id)
 			zombies = append(zombies, id)
-		case id := <-removeZombie:
-			found := false
-			if len(zombies) > 0 {
-				for i := len(zombies) - 1; i >= 0; i-- {
-					if zombies[i] == id {
-						logger.Log(1, "removing zombie from quaratine list", zombies[i].String())
-						zombies = append(zombies[:i], zombies[i+1:]...)
-						found = true
-					}
-				}
-			}
-			if !found {
-				logger.Log(3, "no zombies found")
-			}
-		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
+		case id := <-newHostZombie:
+			hostZombies = append(hostZombies, id)
+		case <-time.After(time.Hour * ZOMBIE_TIMEOUT): // run this check 4 times a day
 			logger.Log(3, "checking for zombie nodes")
 			logger.Log(3, "checking for zombie nodes")
 			if len(zombies) > 0 {
 			if len(zombies) > 0 {
 				for i := len(zombies) - 1; i >= 0; i-- {
 				for i := len(zombies) - 1; i >= 0; i-- {
@@ -92,6 +109,23 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 					}
 					}
 				}
 				}
 			}
 			}
+			if len(hostZombies) > 0 {
+				logger.Log(3, "checking host zombies")
+				for i := len(hostZombies) - 1; i >= 0; i-- {
+					host, err := GetHost(hostZombies[i].String())
+					if err != nil {
+						logger.Log(1, "error retrieving zombie host", err.Error())
+						logger.Log(1, "deleting ", host.ID.String(), " from zombie list")
+						zombies = append(zombies[:i], zombies[i+1:]...)
+						continue
+					}
+					if len(host.Nodes) == 0 {
+						if err := RemoveHost(host); err != nil {
+							logger.Log(0, "error deleting zombie host", host.ID.String(), err.Error())
+						}
+					}
+				}
+			}
 		}
 		}
 	}
 	}
 }
 }
@@ -115,10 +149,10 @@ func InitializeZombies() {
 			}
 			}
 			if node.HostID == othernode.HostID {
 			if node.HostID == othernode.HostID {
 				if node.LastCheckIn.After(othernode.LastCheckIn) {
 				if node.LastCheckIn.After(othernode.LastCheckIn) {
-					zombies = append(zombies, othernode.ID)
+					newZombie <- othernode.ID
 					logger.Log(1, "adding", othernode.ID.String(), "to zombie list")
 					logger.Log(1, "adding", othernode.ID.String(), "to zombie list")
 				} else {
 				} else {
-					zombies = append(zombies, node.ID)
+					newZombie <- node.ID
 					logger.Log(1, "adding", node.ID.String(), "to zombie list")
 					logger.Log(1, "adding", node.ID.String(), "to zombie list")
 				}
 				}
 			}
 			}

+ 27 - 40
main.go

@@ -27,7 +27,7 @@ import (
 	stunserver "github.com/gravitl/netmaker/stun-server"
 	stunserver "github.com/gravitl/netmaker/stun-server"
 )
 )
 
 
-var version = "v0.18.0"
+var version = "v0.18.2"
 
 
 // Start DB Connection and start API Request Handler
 // Start DB Connection and start API Request Handler
 func main() {
 func main() {
@@ -36,12 +36,16 @@ func main() {
 	setupConfig(*absoluteConfigPath)
 	setupConfig(*absoluteConfigPath)
 	servercfg.SetVersion(version)
 	servercfg.SetVersion(version)
 	fmt.Println(models.RetrieveLogo()) // print the logo
 	fmt.Println(models.RetrieveLogo()) // print the logo
-	// fmt.Println(models.ProLogo())
-	initialize() // initial db and acls; gen cert if required
+	initialize()                       // initial db and acls
 	setGarbageCollection()
 	setGarbageCollection()
 	setVerbosity()
 	setVerbosity()
 	defer database.CloseDB()
 	defer database.CloseDB()
-	startControllers() // start the api endpoint and mq
+	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt)
+	defer stop()
+	var waitGroup sync.WaitGroup
+	startControllers(&waitGroup, ctx) // start the api endpoint and mq and stun
+	<-ctx.Done()
+	waitGroup.Wait()
 }
 }
 
 
 func setupConfig(absoluteConfigPath string) {
 func setupConfig(absoluteConfigPath string) {
@@ -110,8 +114,7 @@ func initialize() { // Client Mode Prereq Check
 	}
 	}
 }
 }
 
 
-func startControllers() {
-	var waitnetwork sync.WaitGroup
+func startControllers(wg *sync.WaitGroup, ctx context.Context) {
 	if servercfg.IsDNSMode() {
 	if servercfg.IsDNSMode() {
 		err := logic.SetDNS()
 		err := logic.SetDNS()
 		if err != nil {
 		if err != nil {
@@ -127,48 +130,36 @@ func startControllers() {
 				logger.FatalLog("Unable to Set host. Exiting...", err.Error())
 				logger.FatalLog("Unable to Set host. Exiting...", err.Error())
 			}
 			}
 		}
 		}
-		waitnetwork.Add(1)
-		go controller.HandleRESTRequests(&waitnetwork)
+		wg.Add(1)
+		go controller.HandleRESTRequests(wg, ctx)
 	}
 	}
 	//Run MessageQueue
 	//Run MessageQueue
 	if servercfg.IsMessageQueueBackend() {
 	if servercfg.IsMessageQueueBackend() {
-		waitnetwork.Add(1)
-		go runMessageQueue(&waitnetwork)
+		wg.Add(1)
+		go runMessageQueue(wg, ctx)
 	}
 	}
 
 
-	if !servercfg.IsAgentBackend() && !servercfg.IsRestBackend() && !servercfg.IsMessageQueueBackend() {
-		logger.Log(0, "No Server Mode selected, so nothing is being served! Set Agent mode (AGENT_BACKEND) or Rest mode (REST_BACKEND) or MessageQueue (MESSAGEQUEUE_BACKEND) to 'true'.")
+	if !servercfg.IsRestBackend() && !servercfg.IsMessageQueueBackend() {
+		logger.Log(0, "No Server Mode selected, so nothing is being served! Set Rest mode (REST_BACKEND) or MessageQueue (MESSAGEQUEUE_BACKEND) to 'true'.")
 	}
 	}
 
 
 	// starts the stun server
 	// starts the stun server
-	waitnetwork.Add(1)
-	go stunserver.Start(&waitnetwork)
-	if servercfg.IsProxyEnabled() {
-
-		waitnetwork.Add(1)
-		go func() {
-			defer waitnetwork.Done()
-			_, cancel := context.WithCancel(context.Background())
-			waitnetwork.Add(1)
-
-			//go nmproxy.Start(ctx, logic.ProxyMgmChan, servercfg.GetAPIHost())
-			quit := make(chan os.Signal, 1)
-			signal.Notify(quit, syscall.SIGTERM, os.Interrupt)
-			<-quit
-			cancel()
-		}()
-	}
-
-	waitnetwork.Wait()
+	wg.Add(1)
+	go stunserver.Start(wg, ctx)
 }
 }
 
 
 // Should we be using a context vice a waitgroup????????????
 // Should we be using a context vice a waitgroup????????????
-func runMessageQueue(wg *sync.WaitGroup) {
+func runMessageQueue(wg *sync.WaitGroup, ctx context.Context) {
 	defer wg.Done()
 	defer wg.Done()
-	brokerHost, secure := servercfg.GetMessageQueueEndpoint()
-	logger.Log(0, "connecting to mq broker at", brokerHost, "with TLS?", fmt.Sprintf("%v", secure))
+	brokerHost, _ := servercfg.GetMessageQueueEndpoint()
+	logger.Log(0, "connecting to mq broker at", brokerHost)
 	mq.SetupMQTT()
 	mq.SetupMQTT()
-	ctx, cancel := context.WithCancel(context.Background())
+	if mq.IsConnected() {
+		logger.Log(0, "connected to MQ Broker")
+	} else {
+		logger.FatalLog("error connecting to MQ Broker")
+	}
+	defer mq.CloseClient()
 	go mq.Keepalive(ctx)
 	go mq.Keepalive(ctx)
 	go func() {
 	go func() {
 		peerUpdate := make(chan *models.Node)
 		peerUpdate := make(chan *models.Node)
@@ -179,11 +170,7 @@ func runMessageQueue(wg *sync.WaitGroup) {
 			}
 			}
 		}
 		}
 	}()
 	}()
-	go logic.PurgePendingNodes(ctx)
-	quit := make(chan os.Signal, 1)
-	signal.Notify(quit, syscall.SIGTERM, os.Interrupt)
-	<-quit
-	cancel()
+	<-ctx.Done()
 	logger.Log(0, "Message Queue shutting down")
 	logger.Log(0, "Message Queue shutting down")
 }
 }
 
 

+ 9 - 2
models/api_host.go

@@ -1,6 +1,9 @@
 package models
 package models
 
 
-import "net"
+import (
+	"net"
+	"strings"
+)
 
 
 // ApiHost - the host struct for API usage
 // ApiHost - the host struct for API usage
 type ApiHost struct {
 type ApiHost struct {
@@ -76,7 +79,11 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.ID = currentHost.ID
 	h.ID = currentHost.ID
 	h.HostPass = currentHost.HostPass
 	h.HostPass = currentHost.HostPass
 	h.DaemonInstalled = currentHost.DaemonInstalled
 	h.DaemonInstalled = currentHost.DaemonInstalled
-	h.EndpointIP = net.ParseIP(a.EndpointIP)
+	if len(a.EndpointIP) == 0 || strings.Contains(a.EndpointIP, "nil") {
+		h.EndpointIP = currentHost.EndpointIP
+	} else {
+		h.EndpointIP = net.ParseIP(a.EndpointIP)
+	}
 	h.Debug = a.Debug
 	h.Debug = a.Debug
 	h.FirewallInUse = a.FirewallInUse
 	h.FirewallInUse = a.FirewallInUse
 	h.IPForwarding = currentHost.IPForwarding
 	h.IPForwarding = currentHost.IPForwarding

+ 0 - 3
models/api_node.go

@@ -32,7 +32,6 @@ type ApiNode struct {
 	RelayAddrs              []string `json:"relayaddrs"`
 	RelayAddrs              []string `json:"relayaddrs"`
 	FailoverNode            string   `json:"failovernode"`
 	FailoverNode            string   `json:"failovernode"`
 	DNSOn                   bool     `json:"dnson"`
 	DNSOn                   bool     `json:"dnson"`
-	IsLocal                 bool     `json:"islocal"`
 	Server                  string   `json:"server"`
 	Server                  string   `json:"server"`
 	InternetGateway         string   `json:"internetgateway"`
 	InternetGateway         string   `json:"internetgateway"`
 	Connected               bool     `json:"connected"`
 	Connected               bool     `json:"connected"`
@@ -51,7 +50,6 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.Connected = a.Connected
 	convertedNode.Connected = a.Connected
 	convertedNode.ID, _ = uuid.Parse(a.ID)
 	convertedNode.ID, _ = uuid.Parse(a.ID)
 	convertedNode.HostID, _ = uuid.Parse(a.HostID)
 	convertedNode.HostID, _ = uuid.Parse(a.HostID)
-	convertedNode.IsLocal = a.IsLocal
 	convertedNode.IsRelay = a.IsRelay
 	convertedNode.IsRelay = a.IsRelay
 	convertedNode.IsRelayed = a.IsRelayed
 	convertedNode.IsRelayed = a.IsRelayed
 	convertedNode.PendingDelete = a.PendingDelete
 	convertedNode.PendingDelete = a.PendingDelete
@@ -150,7 +148,6 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
 		apiNode.FailoverNode = ""
 		apiNode.FailoverNode = ""
 	}
 	}
 	apiNode.DNSOn = nm.DNSOn
 	apiNode.DNSOn = nm.DNSOn
-	apiNode.IsLocal = nm.IsLocal
 	apiNode.Server = nm.Server
 	apiNode.Server = nm.Server
 	apiNode.InternetGateway = nm.InternetGateway.String()
 	apiNode.InternetGateway = nm.InternetGateway.String()
 	if isEmptyAddr(apiNode.InternetGateway) {
 	if isEmptyAddr(apiNode.InternetGateway) {

+ 39 - 0
models/dnsEntry.go

@@ -1,6 +1,45 @@
 // TODO:  Either add a returnNetwork and returnKey, or delete this
 // TODO:  Either add a returnNetwork and returnKey, or delete this
 package models
 package models
 
 
+// DNSUpdateAction identifies the action to be performed with the dns update data
+type DNSUpdateAction int
+
+const (
+	// DNSDeleteByIP delete the dns entry
+	DNSDeleteByIP = iota
+	// DNSDeleteByName delete the dns entry
+	DNSDeleteByName
+	// DNSReplaceName replace the dns entry
+	DNSReplaceName
+	// DNSReplaceIP resplace the dns entry
+	DNSReplaceIP
+	// DNSInsert insert a new dns entry
+	DNSInsert
+)
+
+func (action DNSUpdateAction) String() string {
+	return [...]string{"DNSDeleteByIP", "DNSDeletByName", "DNSReplaceName", "DNSReplaceIP", "DNSInsert"}[action]
+}
+
+// DNSError.Error implementation of error interface
+func (e DNSError) Error() string {
+	return "error publishing dns update"
+}
+
+// DNSError error struct capable of holding multiple error messages
+type DNSError struct {
+	ErrorStrings []string
+}
+
+// DNSUpdate data for updating entries in /etc/hosts
+type DNSUpdate struct {
+	Action     DNSUpdateAction
+	Name       string
+	NewName    string
+	Address    string
+	NewAddress string
+}
+
 // DNSEntry - a DNS entry represented as struct
 // DNSEntry - a DNS entry represented as struct
 type DNSEntry struct {
 type DNSEntry struct {
 	Address  string `json:"address" bson:"address" validate:"ip"`
 	Address  string `json:"address" bson:"address" validate:"ip"`

+ 59 - 0
models/enrollment_key.go

@@ -0,0 +1,59 @@
+package models
+
+import (
+	"time"
+)
+
+// EnrollmentToken - the tokenized version of an enrollmentkey;
+// to be used for host registration
+type EnrollmentToken struct {
+	Server string `json:"server"`
+	Value  string `json:"value"`
+}
+
+// EnrollmentKeyLength - the length of an enrollment key - 62^16 unique possibilities
+const EnrollmentKeyLength = 32
+
+// EnrollmentKey - the key used to register hosts and join them to specific networks
+type EnrollmentKey struct {
+	Expiration    time.Time `json:"expiration"`
+	UsesRemaining int       `json:"uses_remaining"`
+	Value         string    `json:"value"`
+	Networks      []string  `json:"networks"`
+	Unlimited     bool      `json:"unlimited"`
+	Tags          []string  `json:"tags"`
+	Token         string    `json:"token,omitempty"` // B64 value of EnrollmentToken
+}
+
+// APIEnrollmentKey - used to create enrollment keys via API
+type APIEnrollmentKey struct {
+	Expiration    int64    `json:"expiration"`
+	UsesRemaining int      `json:"uses_remaining"`
+	Networks      []string `json:"networks"`
+	Unlimited     bool     `json:"unlimited"`
+	Tags          []string `json:"tags"`
+}
+
+// EnrollmentKey.IsValid - checks if the key is still valid to use
+func (k *EnrollmentKey) IsValid() bool {
+	if k == nil {
+		return false
+	}
+	if k.UsesRemaining > 0 {
+		return true
+	}
+	if !k.Expiration.IsZero() && time.Now().Before(k.Expiration) {
+		return true
+	}
+
+	return k.Unlimited
+}
+
+// EnrollmentKey.Validate - validate's an EnrollmentKey
+// should be used during creation
+func (k *EnrollmentKey) Validate() bool {
+	return k.Networks != nil &&
+		k.Tags != nil &&
+		len(k.Value) == EnrollmentKeyLength &&
+		k.IsValid()
+}

+ 4 - 0
models/host.go

@@ -74,6 +74,10 @@ const (
 	DeleteHost = "DELETE_HOST"
 	DeleteHost = "DELETE_HOST"
 	// JoinHostToNetwork - constant for host network join action
 	// JoinHostToNetwork - constant for host network join action
 	JoinHostToNetwork = "JOIN_HOST_TO_NETWORK"
 	JoinHostToNetwork = "JOIN_HOST_TO_NETWORK"
+	// Acknowledgement - ACK response for hosts
+	Acknowledgement = "ACK"
+	// RequestAck - request an ACK
+	RequestAck = "REQ_ACK"
 )
 )
 
 
 // HostUpdate - struct for host update
 // HostUpdate - struct for host update

+ 13 - 27
models/mqtt.go

@@ -6,34 +6,25 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
-// PeerUpdate - struct
-type PeerUpdate struct {
-	Network       string               `json:"network" bson:"network" yaml:"network"`
-	ServerVersion string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
-	ServerAddrs   []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
-	Peers         []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
-	DNS           string               `json:"dns" bson:"dns" yaml:"dns"`
-	PeerIDs       PeerMap              `json:"peerids" bson:"peerids" yaml:"peerids"`
-	ProxyUpdate   ProxyManagerPayload  `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
-}
-
 // HostPeerUpdate - struct for host peer updates
 // HostPeerUpdate - struct for host peer updates
 type HostPeerUpdate struct {
 type HostPeerUpdate struct {
-	Host          Host                   `json:"host" bson:"host" yaml:"host"`
-	Server        string                 `json:"server" bson:"server" yaml:"server"`
-	ServerVersion string                 `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
-	ServerAddrs   []ServerAddr           `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
-	Network       map[string]NetworkInfo `json:"network" bson:"network" yaml:"network"`
-	Peers         []wgtypes.PeerConfig   `json:"peers" bson:"peers" yaml:"peers"`
-	PeerIDs       HostPeerMap            `json:"peerids" bson:"peerids" yaml:"peerids"`
-	ProxyUpdate   ProxyManagerPayload    `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
-	EgressInfo    map[string]EgressInfo  `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
-	IngressInfo   IngressInfo            `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
+	Host          Host                 `json:"host" bson:"host" yaml:"host"`
+	Server        string               `json:"server" bson:"server" yaml:"server"`
+	ServerVersion string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
+	ServerAddrs   []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
+	NodePeers     []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
+	Peers         []wgtypes.PeerConfig
+	HostPeerIDs   HostPeerMap           `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"`
+	ProxyUpdate   ProxyManagerPayload   `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
+	EgressInfo    map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
+	IngressInfo   IngressInfo           `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
+	PeerIDs       PeerMap               `json:"peerids" bson:"peerids" yaml:"peerids"`
 }
 }
 
 
 // IngressInfo - struct for ingress info
 // IngressInfo - struct for ingress info
 type IngressInfo struct {
 type IngressInfo struct {
-	ExtPeers map[string]ExtClientInfo `json:"ext_peers" yaml:"ext_peers"`
+	ExtPeers     map[string]ExtClientInfo `json:"ext_peers" yaml:"ext_peers"`
+	EgressRanges []string                 `json:"egress_ranges" yaml:"egress_ranges"`
 }
 }
 
 
 // EgressInfo - struct for egress info
 // EgressInfo - struct for egress info
@@ -62,11 +53,6 @@ type ExtClientInfo struct {
 	Peers       map[string]PeerRouteInfo `json:"peers" yaml:"peers"`
 	Peers       map[string]PeerRouteInfo `json:"peers" yaml:"peers"`
 }
 }
 
 
-// NetworkInfo - struct for network info
-type NetworkInfo struct {
-	DNS string `json:"dns" bson:"dns" yaml:"dns"`
-}
-
 // KeyUpdate - key update struct
 // KeyUpdate - key update struct
 type KeyUpdate struct {
 type KeyUpdate struct {
 	Network   string `json:"network" bson:"network"`
 	Network   string `json:"network" bson:"network"`

+ 0 - 4
models/network.go

@@ -21,7 +21,6 @@ type Network struct {
 	DefaultKeepalive    int32                 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
 	DefaultKeepalive    int32                 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
 	AccessKeys          []AccessKey           `json:"accesskeys" bson:"accesskeys"`
 	AccessKeys          []AccessKey           `json:"accesskeys" bson:"accesskeys"`
 	AllowManualSignUp   string                `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
 	AllowManualSignUp   string                `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
-	IsLocal             string                `json:"islocal" bson:"islocal" validate:"checkyesorno"`
 	IsIPv4              string                `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
 	IsIPv4              string                `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
 	IsIPv6              string                `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
 	IsIPv6              string                `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
 	DefaultUDPHolePunch string                `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
 	DefaultUDPHolePunch string                `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
@@ -51,9 +50,6 @@ func (network *Network) SetDefaults() {
 	if network.DefaultUDPHolePunch == "" {
 	if network.DefaultUDPHolePunch == "" {
 		network.DefaultUDPHolePunch = "no"
 		network.DefaultUDPHolePunch = "no"
 	}
 	}
-	if network.IsLocal == "" {
-		network.IsLocal = "no"
-	}
 	if network.DefaultInterface == "" {
 	if network.DefaultInterface == "" {
 		if len(network.NetID) < 13 {
 		if len(network.NetID) < 13 {
 			network.DefaultInterface = "nm-" + network.NetID
 			network.DefaultInterface = "nm-" + network.NetID

+ 1 - 1
models/network_test.go

@@ -2,7 +2,7 @@ package models
 
 
 // moved from controllers need work
 // moved from controllers need work
 //func TestUpdateNetwork(t *testing.T) {
 //func TestUpdateNetwork(t *testing.T) {
-//	database.InitializeDatabase()
+//	initialize()
 //	createNet()
 //	createNet()
 //	network := getNet()
 //	network := getNet()
 //	t.Run("NetID", func(t *testing.T) {
 //	t.Run("NetID", func(t *testing.T) {

+ 0 - 14
models/node.go

@@ -68,7 +68,6 @@ type CommonNode struct {
 	Address6            net.IPNet     `json:"address6" yaml:"address6"`
 	Address6            net.IPNet     `json:"address6" yaml:"address6"`
 	Action              string        `json:"action" yaml:"action"`
 	Action              string        `json:"action" yaml:"action"`
 	LocalAddress        net.IPNet     `json:"localaddress" yaml:"localaddress"`
 	LocalAddress        net.IPNet     `json:"localaddress" yaml:"localaddress"`
-	IsLocal             bool          `json:"islocal" yaml:"islocal"`
 	IsEgressGateway     bool          `json:"isegressgateway" yaml:"isegressgateway"`
 	IsEgressGateway     bool          `json:"isegressgateway" yaml:"isegressgateway"`
 	EgressGatewayRanges []string      `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	EgressGatewayRanges []string      `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	IsIngressGateway    bool          `json:"isingressgateway" yaml:"isingressgateway"`
 	IsIngressGateway    bool          `json:"isingressgateway" yaml:"isingressgateway"`
@@ -145,7 +144,6 @@ type LegacyNode struct {
 	DNSOn           string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
 	DNSOn           string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
 	IsServer        string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
 	IsServer        string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
 	Action          string      `json:"action" bson:"action" yaml:"action"`
 	Action          string      `json:"action" bson:"action" yaml:"action"`
-	IsLocal         string      `json:"islocal" bson:"islocal" yaml:"islocal" validate:"checkyesorno"`
 	IPForwarding    string      `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
 	IPForwarding    string      `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
 	OS              string      `json:"os" bson:"os" yaml:"os"`
 	OS              string      `json:"os" bson:"os" yaml:"os"`
 	MTU             int32       `json:"mtu" bson:"mtu" yaml:"mtu"`
 	MTU             int32       `json:"mtu" bson:"mtu" yaml:"mtu"`
@@ -295,13 +293,6 @@ func (node *LegacyNode) SetIPForwardingDefault() {
 	}
 	}
 }
 }
 
 
-// Node.SetIsLocalDefault - set is local default
-func (node *LegacyNode) SetIsLocalDefault() {
-	if node.IsLocal == "" {
-		node.IsLocal = "no"
-	}
-}
-
 // Node.SetDNSOnDefault - sets dns on default
 // Node.SetDNSOnDefault - sets dns on default
 func (node *LegacyNode) SetDNSOnDefault() {
 func (node *LegacyNode) SetDNSOnDefault() {
 	if node.DNSOn == "" {
 	if node.DNSOn == "" {
@@ -408,9 +399,6 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable
 	if newNode.DNSOn != currentNode.DNSOn {
 	if newNode.DNSOn != currentNode.DNSOn {
 		newNode.DNSOn = currentNode.DNSOn
 		newNode.DNSOn = currentNode.DNSOn
 	}
 	}
-	if newNode.IsLocal != currentNode.IsLocal {
-		newNode.IsLocal = currentNode.IsLocal
-	}
 	if newNode.Action == "" {
 	if newNode.Action == "" {
 		newNode.Action = currentNode.Action
 		newNode.Action = currentNode.Action
 	}
 	}
@@ -526,7 +514,6 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) {
 		}
 		}
 	}
 	}
 	node.Action = ln.Action
 	node.Action = ln.Action
-	node.IsLocal = parseBool(ln.IsLocal)
 	node.IsEgressGateway = parseBool(ln.IsEgressGateway)
 	node.IsEgressGateway = parseBool(ln.IsEgressGateway)
 	node.IsIngressGateway = parseBool(ln.IsIngressGateway)
 	node.IsIngressGateway = parseBool(ln.IsIngressGateway)
 	node.DNSOn = parseBool(ln.DNSOn)
 	node.DNSOn = parseBool(ln.DNSOn)
@@ -574,7 +561,6 @@ func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode {
 	l.UDPHolePunch = formatBool(true)
 	l.UDPHolePunch = formatBool(true)
 	l.DNSOn = formatBool(n.DNSOn)
 	l.DNSOn = formatBool(n.DNSOn)
 	l.Action = n.Action
 	l.Action = n.Action
-	l.IsLocal = formatBool(n.IsLocal)
 	l.IPForwarding = formatBool(h.IPForwarding)
 	l.IPForwarding = formatBool(h.IPForwarding)
 	l.OS = h.OS
 	l.OS = h.OS
 	l.MTU = int32(h.MTU)
 	l.MTU = int32(h.MTU)

+ 1 - 1
models/structs.go

@@ -205,7 +205,7 @@ type TrafficKeys struct {
 
 
 // NodeGet - struct for a single node get response
 // NodeGet - struct for a single node get response
 type NodeGet struct {
 type NodeGet struct {
-	Node         LegacyNode           `json:"node" bson:"node" yaml:"node"`
+	Node         Node                 `json:"node" bson:"node" yaml:"node"`
 	Host         Host                 `json:"host" yaml:"host"`
 	Host         Host                 `json:"host" yaml:"host"`
 	Peers        []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
 	Peers        []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
 	HostPeers    []wgtypes.PeerConfig `json:"host_peers" bson:"host_peers" yaml:"host_peers"`
 	HostPeers    []wgtypes.PeerConfig `json:"host_peers" bson:"host_peers" yaml:"host_peers"`

+ 154 - 0
mq/emqx.go

@@ -0,0 +1,154 @@
+package mq
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+type (
+	emqxUser struct {
+		UserID   string `json:"user_id"`
+		Password string `json:"password"`
+		Admin    bool   `json:"is_superuser"`
+	}
+
+	emqxLogin struct {
+		Username string `json:"username"`
+		Password string `json:"password"`
+	}
+
+	emqxLoginResponse struct {
+		License struct {
+			Edition string `json:"edition"`
+		} `json:"license"`
+		Token   string `json:"token"`
+		Version string `json:"version"`
+	}
+)
+
+func getEmqxAuthToken() (string, error) {
+	payload, err := json.Marshal(&emqxLogin{
+		Username: servercfg.GetMqUserName(),
+		Password: servercfg.GetMqPassword(),
+	})
+	if err != nil {
+		return "", err
+	}
+	resp, err := http.Post(servercfg.GetEmqxRestEndpoint()+"/api/v5/login", "application/json", bytes.NewReader(payload))
+	if err != nil {
+		return "", err
+	}
+	msg, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("error during EMQX login %v", string(msg))
+	}
+	var loginResp emqxLoginResponse
+	if err := json.Unmarshal(msg, &loginResp); err != nil {
+		return "", err
+	}
+	return loginResp.Token, nil
+}
+
+// CreateEmqxUser - creates an EMQX user
+func CreateEmqxUser(username, password string, admin bool) error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&emqxUser{
+		UserID:   username,
+		Password: password,
+		Admin:    admin,
+	})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode >= 300 {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error creating EMQX user %v", string(msg))
+	}
+	return nil
+}
+
+// DeleteEmqxUser - deletes an EMQX user
+func DeleteEmqxUser(username string) error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodDelete, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users/"+username, nil)
+	if err != nil {
+		return err
+	}
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode >= 300 {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error deleting EMQX user %v", string(msg))
+	}
+	return nil
+}
+
+// CreateEmqxDefaultAuthenticator - creates a default authenticator based on password and using EMQX's built in database as storage
+func CreateEmqxDefaultAuthenticator() error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&struct {
+		Mechanism  string `json:"mechanism"`
+		Backend    string `json:"backend"`
+		UserIDType string `json:"user_id_type"`
+	}{Mechanism: "password_based", Backend: "built_in_database", UserIDType: "username"})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error creating default EMQX authenticator %v", string(msg))
+	}
+	return nil
+}

+ 23 - 8
mq/handlers.go

@@ -9,6 +9,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
@@ -144,6 +145,19 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
 		logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
 		var sendPeerUpdate bool
 		var sendPeerUpdate bool
 		switch hostUpdate.Action {
 		switch hostUpdate.Action {
+		case models.Acknowledgement:
+			hu := hostactions.GetAction(currentHost.ID.String())
+			if hu != nil {
+				if err = HostUpdate(hu); err != nil {
+					logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+					return
+				} else {
+					if err = PublishSingleHostPeerUpdate(currentHost, nil); err != nil {
+						logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+						return
+					}
+				}
+			}
 		case models.UpdateHost:
 		case models.UpdateHost:
 			sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 			sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 			err := logic.UpsertHost(currentHost)
 			err := logic.UpsertHost(currentHost)
@@ -152,6 +166,13 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 				return
 				return
 			}
 			}
 		case models.DeleteHost:
 		case models.DeleteHost:
+			if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+				// delete EMQX credentials for host
+				if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
+					logger.Log(0, "failed to remove host credentials from EMQX: ", currentHost.ID.String(), err.Error())
+					return
+				}
+			}
 			if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
 			if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
 				logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
 				logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
 				return
 				return
@@ -162,12 +183,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 			}
 			}
 			sendPeerUpdate = true
 			sendPeerUpdate = true
 		}
 		}
-		if sendPeerUpdate {
-			err := PublishPeerUpdate()
-			if err != nil {
-				logger.Log(0, "failed to pulish peer update: ", err.Error())
-			}
-		}
+
 		if sendPeerUpdate {
 		if sendPeerUpdate {
 			err := PublishPeerUpdate()
 			err := PublishPeerUpdate()
 			if err != nil {
 			if err != nil {
@@ -233,11 +249,10 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 				logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
 				logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
 				host, err := logic.GetHost(currentNode.HostID.String())
 				host, err := logic.GetHost(currentNode.HostID.String())
 				if err == nil {
 				if err == nil {
-					if err = PublishSingleHostUpdate(host); err != nil {
+					if err = PublishSingleHostPeerUpdate(host, nil); err != nil {
 						logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
 						logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
 					}
 					}
 				}
 				}
-
 			}
 			}
 
 
 			logger.Log(1, "updated node metrics", id)
 			logger.Log(1, "updated node metrics", id)

+ 17 - 0
mq/mq.go

@@ -2,6 +2,7 @@ package mq
 
 
 import (
 import (
 	"context"
 	"context"
+	"log"
 	"time"
 	"time"
 
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	mqtt "github.com/eclipse/paho.mqtt.golang"
@@ -38,6 +39,17 @@ func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
 
 
 // SetupMQTT creates a connection to broker and return client
 // SetupMQTT creates a connection to broker and return client
 func SetupMQTT() {
 func SetupMQTT() {
+	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+		time.Sleep(10 * time.Second) // wait for the REST endpoint to be ready
+		// setup authenticator and create admin user
+		if err := CreateEmqxDefaultAuthenticator(); err != nil {
+			logger.Log(0, err.Error())
+		}
+		DeleteEmqxUser(servercfg.GetMqUserName())
+		if err := CreateEmqxUser(servercfg.GetMqUserName(), servercfg.GetMqPassword(), true); err != nil {
+			log.Fatal(err)
+		}
+	}
 	opts := mqtt.NewClientOptions()
 	opts := mqtt.NewClientOptions()
 	setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
 	setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
@@ -100,3 +112,8 @@ func Keepalive(ctx context.Context) {
 func IsConnected() bool {
 func IsConnected() bool {
 	return mqclient != nil && mqclient.IsConnected()
 	return mqclient != nil && mqclient.IsConnected()
 }
 }
+
+// CloseClient - function to close the mq connection from server
+func CloseClient() {
+	mqclient.Disconnect(250)
+}

+ 299 - 85
mq/publishers.go

@@ -25,7 +25,7 @@ func PublishPeerUpdate() error {
 	}
 	}
 	for _, host := range hosts {
 	for _, host := range hosts {
 		host := host
 		host := host
-		err = PublishSingleHostUpdate(&host)
+		err = PublishSingleHostPeerUpdate(&host, nil)
 		if err != nil {
 		if err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 		}
@@ -33,13 +33,37 @@ func PublishPeerUpdate() error {
 	return err
 	return err
 }
 }
 
 
-// PublishSingleHostUpdate --- determines and publishes a peer update to one host
-func PublishSingleHostUpdate(host *models.Host) error {
+// PublishDeletedNodePeerUpdate --- determines and publishes a peer update
+// to all the hosts with a deleted node to account for
+func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
+	if !servercfg.IsMessageQueueBackend() {
+		return nil
+	}
+
+	hosts, err := logic.GetAllHosts()
+	if err != nil {
+		logger.Log(1, "err getting all hosts", err.Error())
+		return err
+	}
+	for _, host := range hosts {
+		host := host
+		if err = PublishSingleHostPeerUpdate(&host, delNode); err != nil {
+			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
+		}
+	}
+	return err
+}
+
+// PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
+func PublishSingleHostPeerUpdate(host *models.Host, deletedNode *models.Node) error {
 
 
-	peerUpdate, err := logic.GetPeerUpdateForHost(host)
+	peerUpdate, err := logic.GetPeerUpdateForHost("", host, deletedNode)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	if len(peerUpdate.Peers) == 0 { // no peers to send
+		return nil
+	}
 	if host.ProxyEnabled {
 	if host.ProxyEnabled {
 		proxyUpdate, err := logic.GetProxyUpdateForHost(host)
 		proxyUpdate, err := logic.GetProxyUpdateForHost(host)
 		if err != nil {
 		if err != nil {
@@ -56,13 +80,6 @@ func PublishSingleHostUpdate(host *models.Host) error {
 	return publish(host, fmt.Sprintf("peers/host/%s/%s", host.ID.String(), servercfg.GetServer()), data)
 	return publish(host, fmt.Sprintf("peers/host/%s/%s", host.ID.String(), servercfg.GetServer()), data)
 }
 }
 
 
-// PublishPeerUpdate --- publishes a peer update to all the peers of a node
-func PublishExtPeerUpdate(node *models.Node) error {
-
-	go PublishPeerUpdate()
-	return nil
-}
-
 // NodeUpdate -- publishes a node update
 // NodeUpdate -- publishes a node update
 func NodeUpdate(node *models.Node) error {
 func NodeUpdate(node *models.Node) error {
 	host, err := logic.GetHost(node.HostID.String())
 	host, err := logic.GetHost(node.HostID.String())
@@ -83,7 +100,7 @@ func NodeUpdate(node *models.Node) error {
 		logger.Log(2, "error marshalling node update ", err.Error())
 		logger.Log(2, "error marshalling node update ", err.Error())
 		return err
 		return err
 	}
 	}
-	if err = publish(host, fmt.Sprintf("update/%s/%s", node.Network, node.ID), data); err != nil {
+	if err = publish(host, fmt.Sprintf("node/update/%s/%s", node.Network, node.ID), data); err != nil {
 		logger.Log(2, "error publishing node update to peer ", node.ID.String(), err.Error())
 		logger.Log(2, "error publishing node update to peer ", node.ID.String(), err.Error())
 		return err
 		return err
 	}
 	}
@@ -111,97 +128,195 @@ func HostUpdate(hostUpdate *models.HostUpdate) error {
 	return nil
 	return nil
 }
 }
 
 
-// sendPeers - retrieve networks, send peer ports to all peers
-func sendPeers() {
-
-	hosts, err := logic.GetAllHosts()
+// ServerStartNotify - notifies all non server nodes to pull changes after a restart
+func ServerStartNotify() error {
+	nodes, err := logic.GetAllNodes()
 	if err != nil {
 	if err != nil {
-		logger.Log(1, "error retrieving networks for keepalive", err.Error())
+		return err
+	}
+	for i := range nodes {
+		nodes[i].Action = models.NODE_FORCE_UPDATE
+		if err = NodeUpdate(&nodes[i]); err != nil {
+			logger.Log(1, "error when notifying node", nodes[i].ID.String(), "of a server startup")
+		}
 	}
 	}
+	return nil
+}
 
 
-	var force bool
-	peer_force_send++
-	if peer_force_send == 5 {
-		servercfg.SetHost()
-		force = true
-		peer_force_send = 0
-		err := logic.TimerCheckpoint() // run telemetry & log dumps if 24 hours has passed..
+// PublishDNSUpdate publishes a dns update to all nodes on a network
+func PublishDNSUpdate(network string, dns models.DNSUpdate) error {
+	nodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		return err
+	}
+	for _, node := range nodes {
+		host, err := logic.GetHost(node.HostID.String())
 		if err != nil {
 		if err != nil {
-			logger.Log(3, "error occurred on timer,", err.Error())
+			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			continue
+		}
+		data, err := json.Marshal(dns)
+		if err != nil {
+			logger.Log(0, "failed to encode dns data for node", node.ID.String(), err.Error())
 		}
 		}
+		if err := publish(host, "dns/update/"+host.ID.String()+"/"+servercfg.GetServer(), data); err != nil {
+			logger.Log(0, "error publishing dns update to host", host.ID.String(), err.Error())
+			continue
+		}
+		logger.Log(3, "published dns update to host", host.ID.String())
+	}
+	return nil
+}
 
 
-		//collectServerMetrics(networks[:])
+// PublishAllDNS publishes an array of dns updates (ip / host.network) for each peer to a node joining a network
+func PublishAllDNS(newnode *models.Node) error {
+	alldns := []models.DNSUpdate{}
+	newnodeHost, err := logic.GetHost(newnode.HostID.String())
+	if err != nil {
+		return fmt.Errorf("error retrieving host for dns update %w", err)
+	}
+	alldns = append(alldns, getNodeDNS(newnode.Network)...)
+	alldns = append(alldns, getExtClientDNS(newnode.Network)...)
+	alldns = append(alldns, getCustomDNS(newnode.Network)...)
+	data, err := json.Marshal(alldns)
+	if err != nil {
+		return fmt.Errorf("error encoding dns data %w", err)
 	}
 	}
+	if err := publish(newnodeHost, "dns/all/"+newnodeHost.ID.String()+"/"+servercfg.GetServer(), data); err != nil {
+		return fmt.Errorf("error publishing full dns update to %s, %w", newnodeHost.ID.String(), err)
+	}
+	logger.Log(3, "published full dns update to %s", newnodeHost.ID.String())
+	return nil
+}
 
 
-	for _, host := range hosts {
-		if force {
-			host := host
-			logger.Log(2, "sending scheduled peer update (5 min)")
-			err = PublishSingleHostUpdate(&host)
-			if err != nil {
-				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
-			}
+// PublishDNSDelete publish a dns update deleting a node to all hosts on a network
+func PublishDNSDelete(node *models.Node, host *models.Host) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSDeleteByIP,
+		Name:   host.Name + "." + node.Network,
+	}
+	if node.Address.IP != nil {
+		dns.Address = node.Address.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return fmt.Errorf("dns update node deletion %w", err)
 		}
 		}
 	}
 	}
+	if node.Address6.IP != nil {
+		dns.Address = node.Address6.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return fmt.Errorf("dns update node deletion %w", err)
+		}
+	}
+	return nil
 }
 }
 
 
-// ServerStartNotify - notifies all non server nodes to pull changes after a restart
-func ServerStartNotify() error {
-	nodes, err := logic.GetAllNodes()
-	if err != nil {
-		return err
+// PublishReplaceDNS publish a dns update to replace a dns entry on all hosts in network
+func PublishReplaceDNS(oldNode, newNode *models.Node, host *models.Host) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSReplaceIP,
+		Name:   host.Name + "." + oldNode.Network,
 	}
 	}
-	for i := range nodes {
-		nodes[i].Action = models.NODE_FORCE_UPDATE
-		if err = NodeUpdate(&nodes[i]); err != nil {
-			logger.Log(1, "error when notifying node", nodes[i].ID.String(), "of a server startup")
+	if !oldNode.Address.IP.Equal(newNode.Address.IP) {
+		dns.Address = oldNode.Address.IP.String()
+		dns.NewAddress = newNode.Address.IP.String()
+		if err := PublishDNSUpdate(oldNode.Network, dns); err != nil {
+			return err
+		}
+	}
+	if !oldNode.Address6.IP.Equal(newNode.Address6.IP) {
+		dns.Address = oldNode.Address6.IP.String()
+		dns.NewAddress = newNode.Address6.IP.String()
+		if err := PublishDNSUpdate(oldNode.Network, dns); err != nil {
+			return err
 		}
 		}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-// function to collect and store metrics for server nodes
-//func collectServerMetrics(networks []models.Network) {
-//	if !servercfg.Is_EE {
-//		return
-//	}
-//	if len(networks) > 0 {
-//		for i := range networks {
-//			currentNetworkNodes, err := logic.GetNetworkNodes(networks[i].NetID)
-//			if err != nil {
-//				continue
-//			}
-//			currentServerNodes := logic.GetServerNodes(networks[i].NetID)
-//			if len(currentServerNodes) > 0 {
-//				for i := range currentServerNodes {
-//					if logic.IsLocalServer(&currentServerNodes[i]) {
-//						serverMetrics := logic.CollectServerMetrics(currentServerNodes[i].ID, currentNetworkNodes)
-//						if serverMetrics != nil {
-//							serverMetrics.NodeName = currentServerNodes[i].Name
-//							serverMetrics.NodeID = currentServerNodes[i].ID
-//							serverMetrics.IsServer = "yes"
-//							serverMetrics.Network = currentServerNodes[i].Network
-//							if err = metrics.GetExchangedBytesForNode(&currentServerNodes[i], serverMetrics); err != nil {
-//								logger.Log(1, fmt.Sprintf("failed to update exchanged bytes info for server: %s, err: %v",
-//									currentServerNodes[i].Name, err))
-//							}
-//							updateNodeMetrics(&currentServerNodes[i], serverMetrics)
-//							if err = logic.UpdateMetrics(currentServerNodes[i].ID, serverMetrics); err != nil {
-//								logger.Log(1, "failed to update metrics for server node", currentServerNodes[i].ID)
-//							}
-//							if servercfg.IsMetricsExporter() {
-//								logger.Log(2, "-------------> SERVER METRICS: ", fmt.Sprintf("%+v", serverMetrics))
-//								if err := pushMetricsToExporter(*serverMetrics); err != nil {
-//									logger.Log(2, "failed to push server metrics to exporter: ", err.Error())
-//								}
-//							}
-//						}
-//					}
-//				}
-//			}
-//		}
-//	}
-//}
+// PublishExtClientDNS publish dns update for new extclient
+func PublishExtCLientDNS(client *models.ExtClient) error {
+	errMsgs := models.DNSError{}
+	dns := models.DNSUpdate{
+		Action:  models.DNSInsert,
+		Name:    client.ClientID + "." + client.Network,
+		Address: client.Address,
+	}
+	if client.Address != "" {
+		dns.Address = client.Address
+		if err := PublishDNSUpdate(client.Network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+
+	}
+	if client.Address6 != "" {
+		dns.Address = client.Address6
+		if err := PublishDNSUpdate(client.Network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+	}
+	if len(errMsgs.ErrorStrings) > 0 {
+		return errMsgs
+	}
+	return nil
+}
+
+// PublishExtClientDNSUpdate update for extclient name change
+func PublishExtClientDNSUpdate(old, new models.ExtClient, network string) error {
+	dns := models.DNSUpdate{
+		Action:  models.DNSReplaceName,
+		Name:    old.ClientID + "." + network,
+		NewName: new.ClientID + "." + network,
+	}
+	if err := PublishDNSUpdate(network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishDeleteExtClientDNS publish dns update to delete extclient entry
+func PublishDeleteExtClientDNS(client *models.ExtClient) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSDeleteByName,
+		Name:   client.ClientID + "." + client.Network,
+	}
+	if err := PublishDNSUpdate(client.Network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishCustomDNS publish dns update for new custom dns entry
+func PublishCustomDNS(entry *models.DNSEntry) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSInsert,
+		Name:   entry.Name + "." + entry.Network,
+		//entry.Address6 is never used
+		Address: entry.Address,
+	}
+	if err := PublishDNSUpdate(entry.Network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishHostDNSUpdate publishes dns update on host name change
+func PublishHostDNSUpdate(old, new *models.Host, networks []string) error {
+	errMsgs := models.DNSError{}
+	for _, network := range networks {
+		dns := models.DNSUpdate{
+			Action:  models.DNSReplaceName,
+			Name:    old.Name + "." + network,
+			NewName: new.Name + "." + network,
+		}
+		if err := PublishDNSUpdate(network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+	}
+	if len(errMsgs.ErrorStrings) > 0 {
+		return errMsgs
+	}
+	return nil
+}
 
 
 func pushMetricsToExporter(metrics models.Metrics) error {
 func pushMetricsToExporter(metrics models.Metrics) error {
 	logger.Log(2, "----> Pushing metrics to exporter")
 	logger.Log(2, "----> Pushing metrics to exporter")
@@ -220,3 +335,102 @@ func pushMetricsToExporter(metrics models.Metrics) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+func getNodeDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	nodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		logger.Log(0, "error retreiving network nodes for network", network, err.Error())
+	}
+	for _, node := range nodes {
+		host, err := logic.GetHost(node.HostID.String())
+		if err != nil {
+			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			continue
+		}
+		dns.Action = models.DNSInsert
+		dns.Name = host.Name + "." + node.Network
+		if node.Address.IP != nil {
+			dns.Address = node.Address.IP.String()
+			alldns = append(alldns, dns)
+		}
+		if node.Address6.IP != nil {
+			dns.Address = node.Address6.IP.String()
+			alldns = append(alldns, dns)
+		}
+	}
+	return alldns
+}
+
+func getExtClientDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	clients, err := logic.GetNetworkExtClients(network)
+	if err != nil {
+		logger.Log(0, "error retrieving extclients", err.Error())
+	}
+	for _, client := range clients {
+		dns.Action = models.DNSInsert
+		dns.Name = client.ClientID + "." + client.Network
+		if client.Address != "" {
+			dns.Address = client.Address
+			alldns = append(alldns, dns)
+		}
+		if client.Address6 != "" {
+			dns.Address = client.Address
+			alldns = append(alldns, dns)
+		}
+	}
+	return alldns
+}
+
+func getCustomDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	customdns, err := logic.GetCustomDNS(network)
+	if err != nil {
+		logger.Log(0, "error retrieving custom dns entries", err.Error())
+	}
+	for _, custom := range customdns {
+		dns.Action = models.DNSInsert
+		dns.Address = custom.Address
+		dns.Name = custom.Name + "." + custom.Network
+		alldns = append(alldns, dns)
+	}
+	return alldns
+}
+
+// sendPeers - retrieve networks, send peer ports to all peers
+func sendPeers() {
+
+	hosts, err := logic.GetAllHosts()
+	if err != nil && len(hosts) > 0 {
+		logger.Log(1, "error retrieving networks for keepalive", err.Error())
+	}
+
+	var force bool
+	peer_force_send++
+	if peer_force_send == 5 {
+		servercfg.SetHost()
+		force = true
+		peer_force_send = 0
+		err := logic.TimerCheckpoint() // run telemetry & log dumps if 24 hours has passed..
+		if err != nil {
+			logger.Log(3, "error occurred on timer,", err.Error())
+		}
+
+		//collectServerMetrics(networks[:])
+	}
+
+	for _, host := range hosts {
+		if force {
+			host := host
+			logger.Log(2, "sending scheduled peer update (5 min)")
+			err = PublishSingleHostPeerUpdate(&host, nil)
+			if err != nil {
+				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
+			}
+		}
+	}
+}

+ 0 - 9
netclient/global_settings/globalsettings.go

@@ -1,9 +0,0 @@
-package global_settings
-
-// globalsettings - settings that are global in nature.  Avoids circular dependencies between config loading and usage.
-
-// PublicIPServices - the list of user-specified IP services to use to obtain the node's public IP
-var PublicIPServices map[string]string = make(map[string]string)
-
-// User - holds a user string for joins when using basic auth
-var User string

+ 0 - 78
netclient/ncutils/iface.go

@@ -2,62 +2,8 @@ package ncutils
 
 
 import (
 import (
 	"net"
 	"net"
-
-	"github.com/gravitl/netmaker/models"
 )
 )
 
 
-// IfaceDelta - checks if the new node causes an interface change
-func IfaceDelta(currentNode *models.LegacyNode, newNode *models.LegacyNode) bool {
-	// single comparison statements
-	if newNode.Endpoint != currentNode.Endpoint ||
-		newNode.PublicKey != currentNode.PublicKey ||
-		newNode.Address != currentNode.Address ||
-		newNode.Address6 != currentNode.Address6 ||
-		newNode.IsEgressGateway != currentNode.IsEgressGateway ||
-		newNode.IsIngressGateway != currentNode.IsIngressGateway ||
-		newNode.IsRelay != currentNode.IsRelay ||
-		newNode.ListenPort != currentNode.ListenPort ||
-		newNode.UDPHolePunch != currentNode.UDPHolePunch ||
-		newNode.MTU != currentNode.MTU ||
-		newNode.IsPending != currentNode.IsPending ||
-		newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
-		newNode.DNSOn != currentNode.DNSOn ||
-		newNode.Connected != currentNode.Connected ||
-		len(newNode.AllowedIPs) != len(currentNode.AllowedIPs) {
-		return true
-	}
-
-	// multi-comparison statements
-	if newNode.IsEgressGateway == "yes" {
-		if len(currentNode.EgressGatewayRanges) != len(newNode.EgressGatewayRanges) {
-			return true
-		}
-		for _, address := range newNode.EgressGatewayRanges {
-			if !StringSliceContains(currentNode.EgressGatewayRanges, address) {
-				return true
-			}
-		}
-	}
-
-	if newNode.IsRelay == "yes" {
-		if len(currentNode.RelayAddrs) != len(newNode.RelayAddrs) {
-			return true
-		}
-		for _, address := range newNode.RelayAddrs {
-			if !StringSliceContains(currentNode.RelayAddrs, address) {
-				return true
-			}
-		}
-	}
-
-	for _, address := range newNode.AllowedIPs {
-		if !StringSliceContains(currentNode.AllowedIPs, address) {
-			return true
-		}
-	}
-	return false
-}
-
 // StringSliceContains - sees if a string slice contains a string element
 // StringSliceContains - sees if a string slice contains a string element
 func StringSliceContains(slice []string, item string) bool {
 func StringSliceContains(slice []string, item string) bool {
 	for _, s := range slice {
 	for _, s := range slice {
@@ -68,30 +14,6 @@ func StringSliceContains(slice []string, item string) bool {
 	return false
 	return false
 }
 }
 
 
-// IPNetSliceContains - sees if a string slice contains a string element
-func IPNetSliceContains(slice []net.IPNet, item net.IPNet) bool {
-	for _, s := range slice {
-		if s.String() == item.String() {
-			return true
-		}
-	}
-	return false
-}
-
-// IfaceExists - return true if you can find the iface
-func IfaceExists(ifacename string) bool {
-	localnets, err := net.Interfaces()
-	if err != nil {
-		return false
-	}
-	for _, localnet := range localnets {
-		if ifacename == localnet.Name {
-			return true
-		}
-	}
-	return false
-}
-
 func IpIsPrivate(ipnet net.IP) bool {
 func IpIsPrivate(ipnet net.IP) bool {
 	return ipnet.IsPrivate() || ipnet.IsLoopback()
 	return ipnet.IsPrivate() || ipnet.IsLoopback()
 }
 }

+ 0 - 593
netclient/ncutils/netclientutils.go

@@ -4,559 +4,13 @@ import (
 	"bytes"
 	"bytes"
 	"crypto/rand"
 	"crypto/rand"
 	"encoding/gob"
 	"encoding/gob"
-	"errors"
-	"fmt"
-	"io"
-	"log"
-	"net"
-	"net/http"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"regexp"
-	"runtime"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/c-robinson/iplib"
-
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/global_settings"
-)
-
-var (
-	// Version - version of the netclient
-	Version = "dev"
 )
 )
 
 
-// MAX_NAME_LENGTH - maximum node name length
-const MAX_NAME_LENGTH = 62
-
-// NO_DB_RECORD - error message result
-const NO_DB_RECORD = "no result found"
-
-// NO_DB_RECORDS - error record result
-const NO_DB_RECORDS = "could not find any records"
-
-// LINUX_APP_DATA_PATH - linux path
-const LINUX_APP_DATA_PATH = "/etc/netclient"
-
-// MAC_APP_DATA_PATH - mac path
-const MAC_APP_DATA_PATH = "/Applications/Netclient"
-
-// WINDOWS_APP_DATA_PATH - windows path
-const WINDOWS_APP_DATA_PATH = "C:\\Program Files (x86)\\Netclient"
-
-// WINDOWS_SVC_NAME - service name
-const WINDOWS_SVC_NAME = "netclient"
-
-// NETCLIENT_DEFAULT_PORT - default port
-const NETCLIENT_DEFAULT_PORT = 51821
-
 // DEFAULT_GC_PERCENT - garbage collection percent
 // DEFAULT_GC_PERCENT - garbage collection percent
 const DEFAULT_GC_PERCENT = 10
 const DEFAULT_GC_PERCENT = 10
 
 
-// KEY_SIZE = ideal length for keys
-const KEY_SIZE = 2048
-
-// SetVersion -- set netclient version for use by other packages
-func SetVersion(ver string) {
-	Version = ver
-}
-
-// IsWindows - checks if is windows
-func IsWindows() bool {
-	return runtime.GOOS == "windows"
-}
-
-// IsMac - checks if is a mac
-func IsMac() bool {
-	return runtime.GOOS == "darwin"
-}
-
-// IsLinux - checks if is linux
-func IsLinux() bool {
-	return runtime.GOOS == "linux"
-}
-
-// IsFreeBSD - checks if is freebsd
-func IsFreeBSD() bool {
-	return runtime.GOOS == "freebsd"
-}
-
-// HasWGQuick - checks if WGQuick command is present
-func HasWgQuick() bool {
-	cmd, err := exec.LookPath("wg-quick")
-	return err == nil && cmd != ""
-}
-
-// GetWireGuard - checks if wg is installed
-func GetWireGuard() string {
-	userspace := os.Getenv("WG_QUICK_USERSPACE_IMPLEMENTATION")
-	if userspace != "" && (userspace == "boringtun" || userspace == "wireguard-go") {
-		return userspace
-	}
-	return "wg"
-}
-
-// IsNFTablesPresent - returns true if nftables is present, false otherwise.
-// Does not consider OS, up to the caller to determine if the OS supports nftables/whether this check is valid.
-func IsNFTablesPresent() bool {
-	found := false
-	_, err := exec.LookPath("nft")
-	if err == nil {
-		found = true
-	}
-	return found
-}
-
-// IsIPTablesPresent - returns true if iptables is present, false otherwise
-// Does not consider OS, up to the caller to determine if the OS supports iptables/whether this check is valid.
-func IsIPTablesPresent() bool {
-	found := false
-	_, err := exec.LookPath("iptables")
-	if err == nil {
-		found = true
-	}
-	return found
-}
-
-// IsKernel - checks if running kernel WireGuard
-func IsKernel() bool {
-	// TODO
-	// Replace && true with some config file value
-	// This value should be something like kernelmode, which should be 'on' by default.
-	return IsLinux() && os.Getenv("WG_QUICK_USERSPACE_IMPLEMENTATION") == ""
-}
-
-// IsEmptyRecord - repeat from database
-func IsEmptyRecord(err error) bool {
-	if err == nil {
-		return false
-	}
-	return strings.Contains(err.Error(), NO_DB_RECORD) || strings.Contains(err.Error(), NO_DB_RECORDS)
-}
-
-// GetPublicIP - gets public ip
-func GetPublicIP(api string) (string, error) {
-
-	iplist := []string{"https://ip.client.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
-
-	for network, ipService := range global_settings.PublicIPServices {
-		logger.Log(3, "User provided public IP service defined for network", network, "is", ipService)
-
-		// prepend the user-specified service so it's checked first
-		iplist = append([]string{ipService}, iplist...)
-	}
-	if api != "" {
-		api = "https://" + api + "/api/getip"
-		iplist = append([]string{api}, iplist...)
-	}
-
-	endpoint := ""
-	var err error
-	for _, ipserver := range iplist {
-		client := &http.Client{
-			Timeout: time.Second * 10,
-		}
-		var resp *http.Response
-		resp, err = client.Get(ipserver)
-		if err != nil {
-			continue
-		}
-		defer resp.Body.Close()
-		if resp.StatusCode == http.StatusOK {
-			var bodyBytes []byte
-			bodyBytes, err = io.ReadAll(resp.Body)
-			if err != nil {
-				continue
-			}
-			endpoint = string(bodyBytes)
-			break
-		}
-	}
-	if err == nil && endpoint == "" {
-		err = errors.New("public address not found")
-	}
-	return endpoint, err
-}
-
-// GetMacAddr - get's mac address
-func GetMacAddr() ([]string, error) {
-	ifas, err := net.Interfaces()
-	if err != nil {
-		return nil, err
-	}
-	var as []string
-	for _, ifa := range ifas {
-		a := ifa.HardwareAddr.String()
-		if a != "" {
-			as = append(as, a)
-		}
-	}
-	return as, nil
-}
-
-// GetLocalIP - gets local ip of machine
-func GetLocalIP(localrange string) (string, error) {
-	_, localRange, err := net.ParseCIDR(localrange)
-	if err != nil {
-		return "", err
-	}
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return "", err
-	}
-	var local string
-	found := false
-	for _, i := range ifaces {
-		if i.Flags&net.FlagUp == 0 {
-			continue // interface down
-		}
-		if i.Flags&net.FlagLoopback != 0 {
-			continue // loopback interface
-		}
-		addrs, err := i.Addrs()
-		if err != nil {
-			return "", err
-		}
-		for _, addr := range addrs {
-			var ip net.IP
-			switch v := addr.(type) {
-			case *net.IPNet:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = localRange.Contains(ip)
-				}
-			case *net.IPAddr:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = localRange.Contains(ip)
-				}
-			}
-		}
-	}
-	if !found || local == "" {
-		return "", errors.New("Failed to find local IP in range " + localrange)
-	}
-	return local, nil
-}
-
-// GetNetworkIPMask - Pulls the netmask out of the network
-func GetNetworkIPMask(networkstring string) (string, string, error) {
-	ip, ipnet, err := net.ParseCIDR(networkstring)
-	if err != nil {
-		return "", "", err
-	}
-	ipstring := ip.String()
-	mask := ipnet.Mask
-	maskstring := fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3])
-	// maskstring := ipnet.Mask.String()
-	return ipstring, maskstring, err
-}
-
-// GetFreePort - gets free port of machine
-func GetFreePort(rangestart int32) (int32, error) {
-	addr := net.UDPAddr{}
-	if rangestart == 0 {
-		rangestart = NETCLIENT_DEFAULT_PORT
-	}
-	for x := rangestart; x <= 65535; x++ {
-		addr.Port = int(x)
-		conn, err := net.ListenUDP("udp", &addr)
-		if err != nil {
-			continue
-		}
-		defer conn.Close()
-		return x, nil
-	}
-	return rangestart, errors.New("no free ports")
-}
-
 // == OS PATH FUNCTIONS ==
 // == OS PATH FUNCTIONS ==
 
 
-// GetHomeDirWindows - gets home directory in windows
-func GetHomeDirWindows() string {
-	if IsWindows() {
-		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
-		if home == "" {
-			home = os.Getenv("USERPROFILE")
-		}
-		return home
-	}
-	return os.Getenv("HOME")
-}
-
-// GetNetclientPath - gets netclient path locally
-func GetNetclientPath() string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH
-	} else if IsMac() {
-		return MAC_APP_DATA_PATH
-	} else {
-		return LINUX_APP_DATA_PATH
-	}
-}
-
-// GetSeparator - gets the separator for OS
-func GetSeparator() string {
-	if IsWindows() {
-		return "\\"
-	} else {
-		return "/"
-	}
-}
-
-// GetFileWithRetry - retry getting file X number of times before failing
-func GetFileWithRetry(path string, retryCount int) ([]byte, error) {
-	var data []byte
-	var err error
-	for count := 0; count < retryCount; count++ {
-		data, err = os.ReadFile(path)
-		if err == nil {
-			return data, err
-		} else {
-			logger.Log(1, "failed to retrieve file ", path, ", retrying...")
-			time.Sleep(time.Second >> 2)
-		}
-	}
-	return data, err
-}
-
-// GetNetclientServerPath - gets netclient server path
-func GetNetclientServerPath(server string) string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH + "\\" + server + "\\"
-	} else if IsMac() {
-		return MAC_APP_DATA_PATH + "/" + server + "/"
-	} else {
-		return LINUX_APP_DATA_PATH + "/" + server
-	}
-}
-
-// GetNetclientPathSpecific - gets specific netclient config path
-func GetNetclientPathSpecific() string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH + "\\"
-	} else if IsMac() {
-		return MAC_APP_DATA_PATH + "/config/"
-	} else {
-		return LINUX_APP_DATA_PATH + "/config/"
-	}
-}
-
-func CheckIPAddress(ip string) error {
-	if net.ParseIP(ip) == nil {
-		return fmt.Errorf("ip address %s is invalid", ip)
-	}
-	return nil
-}
-
-// GetNewIface - Gets the name of the real interface created on Mac
-func GetNewIface(dir string) (string, error) {
-	files, _ := os.ReadDir(dir)
-	var newestFile string
-	var newestTime int64 = 0
-	var err error
-	for _, f := range files {
-		fi, err := os.Stat(dir + f.Name())
-		if err != nil {
-			return "", err
-		}
-		currTime := fi.ModTime().Unix()
-		if currTime > newestTime && strings.Contains(f.Name(), ".sock") {
-			newestTime = currTime
-			newestFile = f.Name()
-		}
-	}
-	resultArr := strings.Split(newestFile, ".")
-	if resultArr[0] == "" {
-		err = errors.New("sock file does not exist")
-	}
-	return resultArr[0], err
-}
-
-// GetFileAsString - returns the string contents of a given file
-func GetFileAsString(path string) (string, error) {
-	content, err := os.ReadFile(path)
-	if err != nil {
-		return "", err
-	}
-	return string(content), err
-}
-
-// GetNetclientPathSpecific - gets specific netclient config path
-func GetWGPathSpecific() string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH + "\\"
-	} else {
-		return "/etc/wireguard/"
-	}
-}
-
-// Copy - copies a src file to dest
-func Copy(src, dst string) error {
-	sourceFileStat, err := os.Stat(src)
-	if err != nil {
-		return err
-	}
-
-	if !sourceFileStat.Mode().IsRegular() {
-		return errors.New(src + " is not a regular file")
-	}
-
-	source, err := os.Open(src)
-	if err != nil {
-		return err
-	}
-	defer source.Close()
-
-	destination, err := os.Create(dst)
-	if err != nil {
-		return err
-	}
-	defer destination.Close()
-	_, err = io.Copy(destination, source)
-	if err != nil {
-		return err
-	}
-	err = os.Chmod(dst, 0755)
-
-	return err
-}
-
-// RunsCmds - runs cmds
-func RunCmds(commands []string, printerr bool) error {
-	var err error
-	for _, command := range commands {
-		// prevent panic
-		if len(strings.Trim(command, " ")) == 0 {
-			continue
-		}
-		args := strings.Fields(command)
-		out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
-		if err != nil && printerr {
-			logger.Log(0, "error running command:", command)
-			logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-		}
-	}
-	return err
-}
-
-// FileExists - checks if file exists locally
-func FileExists(f string) bool {
-	info, err := os.Stat(f)
-	if os.IsNotExist(err) {
-		return false
-	}
-	if err != nil && strings.Contains(err.Error(), "not a directory") {
-		return false
-	}
-	if err != nil {
-		logger.Log(0, "error reading file: "+f+", "+err.Error())
-	}
-	return !info.IsDir()
-}
-
-// GetSystemNetworks - get networks locally
-func GetSystemNetworks() ([]string, error) {
-	var networks []string
-	files, err := filepath.Glob(GetNetclientPathSpecific() + "netconfig-*")
-	if err != nil {
-		return nil, err
-	}
-	for _, file := range files {
-		// don't want files such as *.bak, *.swp
-		if filepath.Ext(file) != "" {
-			continue
-		}
-		file := filepath.Base(file)
-		temp := strings.Split(file, "-")
-		networks = append(networks, strings.Join(temp[1:], "-"))
-	}
-	return networks, nil
-}
-
-// ShortenString - Brings string down to specified length. Stops names from being too long
-func ShortenString(input string, length int) string {
-	output := input
-	if len(input) > length {
-		output = input[0:length]
-	}
-	return output
-}
-
-// DNSFormatString - Formats a string with correct usage for DNS
-func DNSFormatString(input string) string {
-	reg, err := regexp.Compile("[^a-zA-Z0-9-]+")
-	if err != nil {
-		logger.Log(0, "error with regex: "+err.Error())
-		return ""
-	}
-	return reg.ReplaceAllString(input, "")
-}
-
-// GetHostname - Gets hostname of machine
-func GetHostname() string {
-	hostname, err := os.Hostname()
-	if err != nil {
-		return ""
-	}
-	if len(hostname) > MAX_NAME_LENGTH {
-		hostname = hostname[0:MAX_NAME_LENGTH]
-	}
-	return hostname
-}
-
-// CheckUID - Checks to make sure user has root privileges
-func CheckUID() {
-	// start our application
-	out, err := RunCmd("id -u", true)
-
-	if err != nil {
-		log.Fatal(out, err)
-	}
-	id, err := strconv.Atoi(string(out[:len(out)-1]))
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	if id != 0 {
-		log.Fatal("This program must be run with elevated privileges (sudo). This program installs a SystemD service and configures WireGuard and networking rules. Please re-run with sudo/root.")
-	}
-}
-
-// CheckFirewall - checks if iptables of nft install, if not exit
-func CheckFirewall() {
-	if !IsIPTablesPresent() && !IsNFTablesPresent() {
-		log.Fatal("neither iptables nor nft is installed - please install one or the other and try again")
-	}
-}
-
-// CheckWG - Checks if WireGuard is installed. If not, exit
-func CheckWG() {
-	uspace := GetWireGuard()
-	if !HasWG() {
-		if uspace == "wg" {
-			log.Fatal("WireGuard not installed. Please install WireGuard (wireguard-tools) and try again.")
-		}
-		logger.Log(0, "running with userspace wireguard: ", uspace)
-	} else if uspace != "wg" {
-		logger.Log(0, "running userspace WireGuard with ", uspace)
-	}
-}
-
-// HasWG - returns true if wg command exists
-func HasWG() bool {
-	var _, err = exec.LookPath("wg")
-	return err == nil
-}
-
 // ConvertKeyToBytes - util to convert a key to bytes to use elsewhere
 // ConvertKeyToBytes - util to convert a key to bytes to use elsewhere
 func ConvertKeyToBytes(key *[32]byte) ([]byte, error) {
 func ConvertKeyToBytes(key *[32]byte) ([]byte, error) {
 	var buffer bytes.Buffer
 	var buffer bytes.Buffer
@@ -579,16 +33,6 @@ func ConvertBytesToKey(data []byte) (*[32]byte, error) {
 	return result, err
 	return result, err
 }
 }
 
 
-// ServerAddrSliceContains - sees if a string slice contains a string element
-func ServerAddrSliceContains(slice []models.ServerAddr, item models.ServerAddr) bool {
-	for _, s := range slice {
-		if s.Address == item.Address && s.IsLeader == item.IsLeader {
-			return true
-		}
-	}
-	return false
-}
-
 // MakeRandomString - generates a random string of len n
 // MakeRandomString - generates a random string of len n
 func MakeRandomString(n int) string {
 func MakeRandomString(n int) string {
 	const validChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 	const validChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -601,40 +45,3 @@ func MakeRandomString(n int) string {
 	}
 	}
 	return string(result)
 	return string(result)
 }
 }
-
-func GetIPNetFromString(ip string) (net.IPNet, error) {
-	var ipnet *net.IPNet
-	var err error
-	// parsing as a CIDR first. If valid CIDR, append
-	if _, cidr, err := net.ParseCIDR(ip); err == nil {
-		ipnet = cidr
-	} else { // parsing as an IP second. If valid IP, check if ipv4 or ipv6, then append
-		if iplib.Version(net.ParseIP(ip)) == 4 {
-			ipnet = &net.IPNet{
-				IP:   net.ParseIP(ip),
-				Mask: net.CIDRMask(32, 32),
-			}
-		} else if iplib.Version(net.ParseIP(ip)) == 6 {
-			ipnet = &net.IPNet{
-				IP:   net.ParseIP(ip),
-				Mask: net.CIDRMask(128, 128),
-			}
-		}
-	}
-	if ipnet == nil {
-		err = errors.New(ip + " is not a valid ip or cidr")
-		return net.IPNet{}, err
-	}
-	return *ipnet, err
-}
-
-// ModPort - Change Node Port if UDP Hole Punching or ListenPort is not free
-func ModPort(node *models.LegacyNode) error {
-	var err error
-	if node.UDPHolePunch == "yes" {
-		node.ListenPort = 0
-	} else {
-		node.ListenPort, err = GetFreePort(node.ListenPort)
-	}
-	return err
-}

+ 0 - 39
netclient/ncutils/netclientutils_darwin.go

@@ -1,39 +0,0 @@
-package ncutils
-
-import (
-	"os/exec"
-	"strings"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-// WHITESPACE_PLACEHOLDER - used with RunCMD - if a path has whitespace, use this to avoid running path as 2 args in RunCMD
-const WHITESPACE_PLACEHOLDER = "+-+-+-+"
-
-// RunCmd - runs a local command
-func RunCmd(command string, printerr bool) (string, error) {
-
-	args := strings.Fields(command)
-	// return whitespace after split
-	for i, arg := range args {
-		args[i] = strings.Replace(arg, WHITESPACE_PLACEHOLDER, " ", -1)
-	}
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", strings.Join(args, " "))
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// RunCmdFormatted - run a command formatted for MacOS
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-	return "", nil
-}
-
-// GetEmbedded - if files required for MacOS, put here
-func GetEmbedded() error {
-	return nil
-}

+ 0 - 50
netclient/ncutils/netclientutils_freebsd.go

@@ -1,50 +0,0 @@
-package ncutils
-
-import (
-	"context"
-	"os/exec"
-	"strings"
-	"syscall"
-	"time"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-// RunCmdFormatted - run a command formatted for freebsd
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-
-	args := strings.Fields(command)
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Start()
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command: ", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// GetEmbedded - if files required for freebsd, put here
-func GetEmbedded() error {
-	return nil
-}
-
-// Runs Commands for FreeBSD
-func RunCmd(command string, printerr bool) (string, error) {
-	args := strings.Fields(command)
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
-	defer cancel()
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
-	go func() {
-		<-ctx.Done()
-		_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
-	}()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}

+ 0 - 32
netclient/ncutils/netclientutils_linux.go

@@ -1,32 +0,0 @@
-package ncutils
-
-import (
-	"fmt"
-	"os/exec"
-	"strings"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-// RunCmd - runs a local command
-func RunCmd(command string, printerr bool) (string, error) {
-	args := strings.Fields(command)
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, fmt.Sprintf("error running command: %s", command))
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// RunCmdFormatted - does nothing for linux
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-	return "", nil
-}
-
-// GetEmbedded - if files required for linux, put here
-func GetEmbedded() error {
-	return nil
-}

+ 0 - 61
netclient/ncutils/netclientutils_windows.go

@@ -1,61 +0,0 @@
-package ncutils
-
-import (
-	"embed"
-	"fmt"
-	"os"
-	"os/exec"
-	"strings"
-	"syscall"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-//go:embed windowsdaemon/winsw.exe
-var winswContent embed.FS
-
-// RunCmd - runs a local command
-func RunCmd(command string, printerr bool) (string, error) {
-	args := strings.Fields(command)
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Wait()
-	//cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: "/C \"" + command + "\""}
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// RunCmd - runs a local command
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-	var comSpec = os.Getenv("COMSPEC")
-	if comSpec == "" {
-		comSpec = os.Getenv("SystemRoot") + "\\System32\\cmd.exe"
-	}
-	cmd := exec.Command(comSpec)
-	cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: "/C \"" + command + "\""}
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// GetEmbedded - Gets the Windows daemon creator
-func GetEmbedded() error {
-	data, err := winswContent.ReadFile("windowsdaemon/winsw.exe")
-	if err != nil {
-		return err
-	}
-	fileName := fmt.Sprintf("%swinsw.exe", GetNetclientPathSpecific())
-	err = os.WriteFile(fileName, data, 0700)
-	if err != nil {
-		logger.Log(0, "could not mount winsw.exe")
-		return err
-	}
-	return nil
-}

+ 0 - 97
netclient/ncutils/peerhelper.go

@@ -1,97 +0,0 @@
-package ncutils
-
-import (
-	"net"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/gravitl/netmaker/logger"
-	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
-)
-
-// GetPeers - gets the peers from a given WireGuard interface
-func GetPeers(iface string) ([]wgtypes.Peer, error) {
-
-	var peers []wgtypes.Peer
-	output, err := RunCmd("wg show "+iface+" dump", true)
-	if err != nil {
-		return peers, err
-	}
-	for i, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
-		if i == 0 {
-			continue
-		}
-		var allowedIPs []net.IPNet
-		fields := strings.Fields(line)
-		if len(fields) < 4 {
-			logger.Log(0, "error parsing peer: "+line)
-			continue
-		}
-		pubkeystring := fields[0]
-		endpointstring := fields[2]
-		allowedipstring := fields[3]
-		var pkeepalivestring string
-		if len(fields) > 7 {
-			pkeepalivestring = fields[7]
-		}
-		// AllowedIPs = private IP + defined networks
-
-		pubkey, err := wgtypes.ParseKey(pubkeystring)
-		if err != nil {
-			logger.Log(0, "error parsing peer key "+pubkeystring)
-			continue
-		}
-		ipstrings := strings.Split(allowedipstring, ",")
-		for _, ipstring := range ipstrings {
-			var netip net.IP
-			if netip = net.ParseIP(strings.Split(ipstring, "/")[0]); netip != nil {
-				allowedIPs = append(
-					allowedIPs,
-					net.IPNet{
-						IP:   netip,
-						Mask: netip.DefaultMask(),
-					},
-				)
-			}
-		}
-		if len(allowedIPs) == 0 {
-			logger.Log(0, "error parsing peer "+pubkeystring+", no allowedips found")
-			continue
-		}
-		var endpointarr []string
-		var endpointip net.IP
-		if endpointarr = strings.Split(endpointstring, ":"); len(endpointarr) != 2 {
-			logger.Log(0, "error parsing peer "+pubkeystring+", could not parse endpoint: "+endpointstring)
-			continue
-		}
-		if endpointip = net.ParseIP(endpointarr[0]); endpointip == nil {
-			logger.Log(0, "error parsing peer "+pubkeystring+", could not parse endpoint: "+endpointarr[0])
-			continue
-		}
-		var port int
-		if port, err = strconv.Atoi(endpointarr[1]); err != nil {
-			logger.Log(0, "error parsing peer "+pubkeystring+", could not parse port: "+err.Error())
-			continue
-		}
-		var endpoint = net.UDPAddr{
-			IP:   endpointip,
-			Port: port,
-		}
-		var dur time.Duration
-		if pkeepalivestring != "" {
-			if dur, err = time.ParseDuration(pkeepalivestring + "s"); err != nil {
-				logger.Log(0, "error parsing peer "+pubkeystring+", could not parse keepalive: "+err.Error())
-			}
-		}
-
-		peers = append(peers, wgtypes.Peer{
-			PublicKey:                   pubkey,
-			Endpoint:                    &endpoint,
-			AllowedIPs:                  allowedIPs,
-			PersistentKeepaliveInterval: dur,
-		})
-	}
-
-	return peers, err
-}

+ 0 - 46
netclient/ncutils/pid.go

@@ -1,46 +0,0 @@
-package ncutils
-
-import (
-	"fmt"
-	"os"
-	"strconv"
-)
-
-// PIDFILE - path/name of pid file
-const PIDFILE = "/var/run/netclient.pid"
-
-// WindowsPIDError - error returned from pid function on windows
-type WindowsPIDError struct{}
-
-// Error generates error for windows os
-func (*WindowsPIDError) Error() string {
-	return "pid tracking not supported on windows"
-}
-
-// SavePID - saves the pid of running program to disk
-func SavePID() error {
-	if IsWindows() {
-		return nil
-	}
-	pid := os.Getpid()
-	if err := os.WriteFile(PIDFILE, []byte(fmt.Sprintf("%d", pid)), 0644); err != nil {
-		return fmt.Errorf("could not write to pid file %w", err)
-	}
-	return nil
-}
-
-// ReadPID - reads a previously saved pid from disk
-func ReadPID() (int, error) {
-	if IsWindows() {
-		return 0, nil
-	}
-	bytes, err := os.ReadFile(PIDFILE)
-	if err != nil {
-		return 0, fmt.Errorf("could not read pid file %w", err)
-	}
-	pid, err := strconv.Atoi(string(bytes))
-	if err != nil {
-		return 0, fmt.Errorf("pid file contents invalid %w", err)
-	}
-	return pid, nil
-}

+ 0 - 25
netclient/ncutils/util.go

@@ -1,29 +1,4 @@
 package ncutils
 package ncutils
 
 
-import (
-	"fmt"
-	"time"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
 // CheckInInterval - the interval for check-in time in units/minute
 // CheckInInterval - the interval for check-in time in units/minute
 const CheckInInterval = 1
 const CheckInInterval = 1
-
-// BackOff - back off any function while there is an error
-func BackOff(isExponential bool, maxTime int, f interface{}) (interface{}, error) {
-	// maxTime seconds
-	startTime := time.Now()
-	sleepTime := time.Second
-	for time.Now().Before(startTime.Add(time.Second * time.Duration(maxTime))) {
-		if result, err := f.(func() (interface{}, error))(); err == nil {
-			return result, nil
-		}
-		time.Sleep(sleepTime)
-		if isExponential {
-			sleepTime = sleepTime << 1
-		}
-		logger.Log(1, "retrying...")
-	}
-	return nil, fmt.Errorf("could not find result")
-}

BIN
netclient/ncutils/windowsdaemon/winsw.exe


+ 7 - 0
release.md

@@ -0,0 +1,7 @@
+# Netmaker v0.18.1
+
+## whats new
+
+## whats fixed
+
+## known issues

+ 146 - 55
scripts/nm-quick-interactive.sh → scripts/nm-quick.sh

@@ -1,5 +1,8 @@
 #!/bin/bash
 #!/bin/bash
 
 
+LATEST="v0.18.2"
+
+print_logo() {(
 cat << "EOF"
 cat << "EOF"
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -16,13 +19,82 @@ cat << "EOF"
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 EOF
 EOF
+)}
 
 
 if [ $(id -u) -ne 0 ]; then
 if [ $(id -u) -ne 0 ]; then
    echo "This script must be run as root"
    echo "This script must be run as root"
    exit 1
    exit 1
 fi
 fi
 
 
-if [ -z "$1" ]; then
+unset INSTALL_TYPE
+unset BUILD_TYPE
+unset BUILD_TAG
+unset IMAGE_TAG
+
+usage () {(
+    echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag]"
+    echo "  -e      if specified, will install netmaker EE"
+    echo "  -b      type of build; options:"
+	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
+	echo "          \"local\": - will install by cloning repo and and building images from git"
+	echo "          \"branch\": - will install a specific branch using remote git and dockerhub "
+    echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
+    echo "examples:"
+	echo "          nm-quick.sh -e -b version -t v0.18.2"
+	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"	
+	echo "          nm-quick.sh -e -b branch -t develop"
+    exit 1
+)}
+
+while getopts evb:t: flag
+do
+    case "${flag}" in
+        e) 
+			INSTALL_TYPE="ee"
+			;;
+		v) 
+			usage
+			exit 0
+			;;
+        b) 
+			BUILD_TYPE=${OPTARG}
+			if [[ ! "$BUILD_TYPE" =~ ^(version|local|branch)$ ]]; then
+    			echo "error: $BUILD_TYPE is invalid"
+				echo "valid options: version, local, branch"
+				usage
+				exit 1
+			fi
+			;;
+        t) 
+			BUILD_TAG=${OPTARG}
+			;;
+    esac
+done
+
+if [ -z "$BUILD_TYPE" ]; then
+	BUILD_TYPE="version"
+	BUILD_TAG=$LATEST
+fi
+
+if [ -z "$BUILD_TAG" ] && [ "$BUILD_TYPE" = "version" ]; then
+	BUILD_TAG=$LATEST
+fi
+
+if [ -z "$BUILD_TAG" ] && [ ! -z "$BUILD_TYPE" ]; then
+	echo "error: must specify build tag when build type \"$BUILD_TYPE\" is specified"
+	usage		
+	exit 1
+fi
+
+IMAGE_TAG=$(sed 's/\//-/g' <<< "$BUILD_TAG")
+
+if [ "$1" = "ce" ]; then
+	INSTALL_TYPE="ce"
+elif [ "$1" = "ee" ]; then
+	INSTALL_TYPE="ee"
+fi
+
+if [ -z "$INSTALL_TYPE" ]; then
 	echo "-----------------------------------------------------"
 	echo "-----------------------------------------------------"
 	echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
 	echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
 	echo "EE will require you to create an account at https://dashboard.license.netmaker.io"
 	echo "EE will require you to create an account at https://dashboard.license.netmaker.io"
@@ -42,16 +114,15 @@ if [ -z "$1" ]; then
 		*) echo "invalid option $REPLY";;
 		*) echo "invalid option $REPLY";;
 	esac
 	esac
 	done
 	done
-elif [ "$1" = "ce" ]; then
-	echo "installing Netmaker CE"
-	INSTALL_TYPE="ce"
-elif [ "$1" = "ee" ]; then
-	echo "installing Netmaker EE"
-	INSTALL_TYPE="ee"
-else
-	echo "install type invalid (options: 'ce, ee')"
-	exit 1
 fi
 fi
+echo "-----------Build Options-----------------------------"
+echo "    EE or CE: $INSTALL_TYPE";
+echo "  Build Type: $BUILD_TYPE";
+echo "   Build Tag: $BUILD_TAG";
+echo "   Image Tag: $IMAGE_TAG";
+echo "-----------------------------------------------------"
+
+print_logo
 
 
 wait_seconds() {(
 wait_seconds() {(
   for ((a=1; a <= $1; a++))
   for ((a=1; a <= $1; a++))
@@ -72,45 +143,67 @@ confirm() {(
   done
   done
 )}
 )}
 
 
+local_install_setup() {(
+	rm -rf netmaker-tmp
+	mkdir netmaker-tmp
+	cd netmaker-tmp
+	git clone https://www.github.com/gravitl/netmaker
+	cd netmaker
+	git checkout $BUILD_TAG
+	git pull origin $BUILD_TAG
+	docker build --no-cache --build-arg version=$IMAGE_TAG -t gravitl/netmaker:$IMAGE_TAG .
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		cp compose/docker-compose.ee.yml /root/docker-compose.yml 
+		cp docker/Caddyfile-EE /root/Caddyfile
+	else
+		cp compose/docker-compose.yml /root/docker-compose.yml 
+		cp docker/Caddyfile /root/Caddyfile
+	fi
+	cp docker/mosquitto.conf /root/mosquitto.conf
+	cp docker/wait.sh /root/wait.sh
+	cd ../../
+	rm -rf netmaker-tmp
+)}
+
 echo "checking dependencies..."
 echo "checking dependencies..."
 
 
 OS=$(uname)
 OS=$(uname)
 
 
 if [ -f /etc/debian_version ]; then
 if [ -f /etc/debian_version ]; then
-	dependencies="wireguard wireguard-tools jq docker.io docker-compose"
+	dependencies="git wireguard wireguard-tools jq docker.io docker-compose"
 	update_cmd='apt update'
 	update_cmd='apt update'
 	install_cmd='apt-get install -y'
 	install_cmd='apt-get install -y'
 elif [ -f /etc/alpine-release ]; then
 elif [ -f /etc/alpine-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='apk update'
 	update_cmd='apk update'
 	install_cmd='apk --update add'
 	install_cmd='apk --update add'
 elif [ -f /etc/centos-release ]; then
 elif [ -f /etc/centos-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='yum update'
 	update_cmd='yum update'
 	install_cmd='yum install -y'
 	install_cmd='yum install -y'
 elif [ -f /etc/fedora-release ]; then
 elif [ -f /etc/fedora-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='dnf update'
 	update_cmd='dnf update'
 	install_cmd='dnf install -y'
 	install_cmd='dnf install -y'
 elif [ -f /etc/redhat-release ]; then
 elif [ -f /etc/redhat-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='yum update'
 	update_cmd='yum update'
 	install_cmd='yum install -y'
 	install_cmd='yum install -y'
 elif [ -f /etc/arch-release ]; then
 elif [ -f /etc/arch-release ]; then
-    	dependecies="wireguard-tools jq docker.io docker-compose"
+    	dependecies="git wireguard-tools jq docker.io docker-compose"
 	update_cmd='pacman -Sy'
 	update_cmd='pacman -Sy'
 	install_cmd='pacman -S --noconfirm'
 	install_cmd='pacman -S --noconfirm'
 elif [ "${OS}" = "FreeBSD" ]; then
 elif [ "${OS}" = "FreeBSD" ]; then
-	dependencies="wireguard wget jq docker.io docker-compose"
+	dependencies="git wireguard wget jq docker.io docker-compose"
 	update_cmd='pkg update'
 	update_cmd='pkg update'
 	install_cmd='pkg install -y'
 	install_cmd='pkg install -y'
 elif [ -f /etc/turris-version ]; then
 elif [ -f /etc/turris-version ]; then
-	dependencies="wireguard-tools bash jq docker.io docker-compose"
+	dependencies="git wireguard-tools bash jq docker.io docker-compose"
 	OS="TurrisOS"
 	OS="TurrisOS"
 	update_cmd='opkg update'	
 	update_cmd='opkg update'	
 	install_cmd='opkg install'
 	install_cmd='opkg install'
 elif [ -f /etc/openwrt_release ]; then
 elif [ -f /etc/openwrt_release ]; then
-	dependencies="wireguard-tools bash jq docker.io docker-compose"
+	dependencies="git wireguard-tools bash jq docker.io docker-compose"
 	OS="OpenWRT"
 	OS="OpenWRT"
 	update_cmd='opkg update'	
 	update_cmd='opkg update'	
 	install_cmd='opkg install'
 	install_cmd='opkg install'
@@ -182,11 +275,21 @@ echo "-----------------------------------------------------"
 
 
 wait_seconds 3
 wait_seconds 3
 
 
+
+if [ "$BUILD_TYPE" = "local" ]; then
+	local_install_setup
+fi
+
 set -e
 set -e
 
 
-NETMAKER_BASE_DOMAIN=nm.$(curl -s ifconfig.me | tr . -).nip.io
+IP_ADDR=$(dig -4 myip.opendns.com @resolver1.opendns.com +short)
+if [ "$IP_ADDR" = "" ]; then
+	IP_ADDR=$(curl -s ifconfig.me)
+fi
+
+NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
 COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
 COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
-SERVER_PUBLIC_IP=$(curl -s ifconfig.me)
+SERVER_PUBLIC_IP=$IP_ADDR
 MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
 MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
 DOMAIN_TYPE=""
 DOMAIN_TYPE=""
 echo "-----------------------------------------------------"
 echo "-----------------------------------------------------"
@@ -335,15 +438,18 @@ wait_seconds 3
 
 
 echo "Pulling config files..."
 echo "Pulling config files..."
 
 
-COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.yml" 
-CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile"
+
+COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.yml" 
+CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile"
 if [ "$INSTALL_TYPE" = "ee" ]; then
 if [ "$INSTALL_TYPE" = "ee" ]; then
-	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.ee.yml" 
-	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile-EE"
+	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.ee.yml" 
+	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile-EE"
+fi
+if [ ! "$BUILD_TYPE" = "local" ]; then
+	wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
+	wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/wait.sh
 fi
 fi
 
 
-wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
-wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh
 chmod +x /root/wait.sh
 chmod +x /root/wait.sh
 mkdir -p /etc/netmaker
 mkdir -p /etc/netmaker
 
 
@@ -360,6 +466,19 @@ if [ "$INSTALL_TYPE" = "ee" ]; then
 	sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
 	sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
 	sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
 	sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
 fi
 fi
+
+if [ "$BUILD_TYPE" = "version" ] && [ "$INSTALL_TYPE" = "ee" ]; then
+	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG-ee/g" /root/docker-compose.yml
+else
+	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+fi
+
+if [ "$BUILD_TYPE" = "local" ]; then
+	sed -i "s/REPLACE_UI_IMAGE_TAG/$LATEST/g" /root/docker-compose.yml
+else
+	sed -i "s/REPLACE_UI_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+fi
+
 echo "Starting containers..."
 echo "Starting containers..."
 
 
 docker-compose -f /root/docker-compose.yml up -d
 docker-compose -f /root/docker-compose.yml up -d
@@ -409,34 +528,6 @@ ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
 
 
 wait_seconds 3
 wait_seconds 3
 
 
-echo "Configuring netmaker server as ingress gateway"
-
-for i in 1 2 3 4 5 6
-do
-	echo "    waiting for server node to become available"
-	wait_seconds 10
-	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})
-	echo "    Server ID: $SERVER_ID"
-	if [ $SERVER_ID == "null" ]; then
-		SERVER_ID=""
-	fi
-	if [[ "$i" -ge "6" && -z "$SERVER_ID" ]]; then
-		echo "    Netmaker is having issues configuring itself, please investigate (docker logs netmaker)"
-		echo "    Exiting..."
-		exit 1
-	elif [ -z "$SERVER_ID" ]; then
-		echo "    server node not yet configured, retrying..."
-	elif [[ ! -z "$SERVER_ID" ]]; then
-		echo "    server node is now availble, continuing"
-		break
-	fi
-done
-
-
-if [[ ! -z "$SERVER_ID"  ]]; then
-	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
-fi 
 )}
 )}
 
 
 set +e
 set +e

+ 3 - 5
scripts/nm-upgrade.sh

@@ -203,7 +203,7 @@ collect_server_settings() {
 
 
   STUN_DOMAIN="stun.$SERVER_NAME"
   STUN_DOMAIN="stun.$SERVER_NAME"
   echo "-----------------------------------------------------"
   echo "-----------------------------------------------------"
-  echo "Netmaker v0.18.0 requires a new DNS entry for $STUN_DOMAIN."
+  echo "Netmaker v0.18.2 requires a new DNS entry for $STUN_DOMAIN."
   echo "Please confirm this is added to your DNS provider before continuing"
   echo "Please confirm this is added to your DNS provider before continuing"
   echo "(note: this is not required if using an nip.io address)"
   echo "(note: this is not required if using an nip.io address)"
   echo "-----------------------------------------------------"
   echo "-----------------------------------------------------"
@@ -354,8 +354,7 @@ set_compose() {
   sed -i "s/v0.17.1/testing/g" /root/docker-compose.yml
   sed -i "s/v0.17.1/testing/g" /root/docker-compose.yml
 
 
   # RELEASE_REPLACE - Use this once release is ready
   # RELEASE_REPLACE - Use this once release is ready
-  # sed -i "s/v0.17.1/v0.18.0/g" /root/docker-compose.yml
-
+  # sed -i "s/v0.17.1/v0.18.1/g" /root/docker-compose.yml
   yq ".services.netmaker.environment.SERVER_NAME = \"$SERVER_NAME\"" -i /root/docker-compose.yml
   yq ".services.netmaker.environment.SERVER_NAME = \"$SERVER_NAME\"" -i /root/docker-compose.yml
   yq ".services.netmaker.environment += {\"BROKER_NAME\": \"$BROKER_NAME\"}" -i /root/docker-compose.yml  
   yq ".services.netmaker.environment += {\"BROKER_NAME\": \"$BROKER_NAME\"}" -i /root/docker-compose.yml  
   yq ".services.netmaker.environment += {\"STUN_DOMAIN\": \"$STUN_DOMAIN\"}" -i /root/docker-compose.yml  
   yq ".services.netmaker.environment += {\"STUN_DOMAIN\": \"$STUN_DOMAIN\"}" -i /root/docker-compose.yml  
@@ -564,11 +563,10 @@ join_networks() {
   fi
   fi
 }
 }
 
 
-
 cat << "EOF"
 cat << "EOF"
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-The Netmaker Upgrade Script: Upgrading to v0.18.0 so you don't have to!
+The Netmaker Upgrade Script: Upgrading to v0.18.2 so you don't have to!
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 EOF
 EOF

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