Browse Source

saving docker compose changes

root 4 years ago
parent
commit
1001d43abe
100 changed files with 5726 additions and 5 deletions
  1. 26 0
      compose/docker-compose.localserver.yml
  2. 54 0
      compose/docker-compose.noclient.yml
  3. 1 2
      compose/docker-compose.nodns.yml
  4. 78 0
      compose/docker-compose.reference.yml
  5. 42 0
      compose/docker-compose.server-only.yml
  6. 43 0
      compose/docker-compose.slim.yml
  7. 5 3
      compose/docker-compose.yml
  8. 15 0
      controllers/extClientHttpController.go
  9. 20 0
      docs/Makefile
  10. BIN
      docs/_build/doctrees/about.doctree
  11. BIN
      docs/_build/doctrees/api.doctree
  12. BIN
      docs/_build/doctrees/architecture.doctree
  13. BIN
      docs/_build/doctrees/client-installation.doctree
  14. BIN
      docs/_build/doctrees/conduct.doctree
  15. BIN
      docs/_build/doctrees/contact.doctree
  16. BIN
      docs/_build/doctrees/contribute.doctree
  17. BIN
      docs/_build/doctrees/environment.pickle
  18. BIN
      docs/_build/doctrees/external-clients.doctree
  19. BIN
      docs/_build/doctrees/getting-started.doctree
  20. BIN
      docs/_build/doctrees/index.doctree
  21. BIN
      docs/_build/doctrees/introduction.doctree
  22. BIN
      docs/_build/doctrees/license.doctree
  23. BIN
      docs/_build/doctrees/quick-start.doctree
  24. BIN
      docs/_build/doctrees/server-installation.doctree
  25. BIN
      docs/_build/doctrees/support.doctree
  26. BIN
      docs/_build/doctrees/troubleshoot.doctree
  27. BIN
      docs/_build/doctrees/tutorials.doctree
  28. BIN
      docs/_build/doctrees/usage.doctree
  29. BIN
      docs/_build/doctrees/walkthroughs.doctree
  30. 4 0
      docs/_build/html/.buildinfo
  31. BIN
      docs/_build/html/_images/access-key.png
  32. BIN
      docs/_build/html/_images/create-user.png
  33. BIN
      docs/_build/html/_images/default-net.png
  34. BIN
      docs/_build/html/_images/mesh.png
  35. BIN
      docs/_build/html/_images/nc-install-output.png
  36. BIN
      docs/_build/html/_images/netmaker.png
  37. BIN
      docs/_build/html/_images/nm-diagram.jpg
  38. BIN
      docs/_build/html/_images/nm-node-success.png
  39. BIN
      docs/_build/html/_images/node-details.png
  40. BIN
      docs/_build/html/_images/nodes.png
  41. BIN
      docs/_build/html/_images/ping-node.png
  42. 46 0
      docs/_build/html/_sources/about.rst.txt
  43. 184 0
      docs/_build/html/_sources/api.rst.txt
  44. 176 0
      docs/_build/html/_sources/architecture.rst.txt
  45. 105 0
      docs/_build/html/_sources/client-installation.rst.txt
  46. 77 0
      docs/_build/html/_sources/conduct.rst.txt
  47. 8 0
      docs/_build/html/_sources/contact.rst.txt
  48. 26 0
      docs/_build/html/_sources/contribute.rst.txt
  49. 19 0
      docs/_build/html/_sources/external-clients.rst.txt
  50. 148 0
      docs/_build/html/_sources/getting-started.rst.txt
  51. 160 0
      docs/_build/html/_sources/index.rst.txt
  52. 47 0
      docs/_build/html/_sources/introduction.rst.txt
  53. 6 0
      docs/_build/html/_sources/license.rst.txt
  54. 137 0
      docs/_build/html/_sources/quick-start.rst.txt
  55. 316 0
      docs/_build/html/_sources/server-installation.rst.txt
  56. 69 0
      docs/_build/html/_sources/support.rst.txt
  57. 19 0
      docs/_build/html/_sources/troubleshoot.rst.txt
  58. 17 0
      docs/_build/html/_sources/tutorials.rst.txt
  59. 39 0
      docs/_build/html/_sources/usage.rst.txt
  60. 37 0
      docs/_build/html/_sources/walkthroughs.rst.txt
  61. 904 0
      docs/_build/html/_static/basic.css
  62. 321 0
      docs/_build/html/_static/doctools.js
  63. 12 0
      docs/_build/html/_static/documentation_options.js
  64. BIN
      docs/_build/html/_static/file.png
  65. 3 0
      docs/_build/html/_static/fonts/font-awesome.css
  66. 13 0
      docs/_build/html/_static/fonts/material-icons.css
  67. BIN
      docs/_build/html/_static/fonts/specimen/FontAwesome.ttf
  68. BIN
      docs/_build/html/_static/fonts/specimen/FontAwesome.woff
  69. BIN
      docs/_build/html/_static/fonts/specimen/FontAwesome.woff2
  70. BIN
      docs/_build/html/_static/fonts/specimen/MaterialIcons-Regular.ttf
  71. BIN
      docs/_build/html/_static/fonts/specimen/MaterialIcons-Regular.woff
  72. BIN
      docs/_build/html/_static/fonts/specimen/MaterialIcons-Regular.woff2
  73. BIN
      docs/_build/html/_static/images/favicon.png
  74. 1 0
      docs/_build/html/_static/images/icons/bitbucket.1b09e088.svg
  75. 1 0
      docs/_build/html/_static/images/icons/bitbucket.svg
  76. 1 0
      docs/_build/html/_static/images/icons/github.f0b8504a.svg
  77. 1 0
      docs/_build/html/_static/images/icons/github.svg
  78. 1 0
      docs/_build/html/_static/images/icons/gitlab.6dd19c00.svg
  79. 1 0
      docs/_build/html/_static/images/icons/gitlab.svg
  80. 2540 0
      docs/_build/html/_static/javascripts/application.js
  81. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.da.js
  82. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.de.js
  83. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.du.js
  84. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.es.js
  85. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.fi.js
  86. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.fr.js
  87. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.hu.js
  88. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.it.js
  89. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.ja.js
  90. 1 0
      docs/_build/html/_static/javascripts/lunr/lunr.jp.js
  91. 1 0
      docs/_build/html/_static/javascripts/lunr/lunr.multi.js
  92. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.nl.js
  93. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.no.js
  94. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.pt.js
  95. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.ro.js
  96. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.ru.js
  97. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.stemmer.support.js
  98. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.sv.js
  99. 1 0
      docs/_build/html/_static/javascripts/lunr/lunr.th.js
  100. 0 0
      docs/_build/html/_static/javascripts/lunr/lunr.tr.js

+ 26 - 0
compose/docker-compose.localserver.yml

@@ -0,0 +1,26 @@
+version: "3.4"
+
+volumes:
+  dnsconfig:
+  driver: local
+services:
+  mongodb:
+    image: mongo:4.2
+    ports:
+      - "27017:27017"
+    container_name: mongodb
+    volumes:
+      - mongovol:/data/db
+    restart: always
+    environment:
+      MONGO_INITDB_ROOT_USERNAME: mongoadmin
+      MONGO_INITDB_ROOT_PASSWORD: mongopass
+  netmaker-ui:
+    container_name: netmaker-ui
+    image: gravitl/netmaker-ui:v0.3
+    ports:
+      - "80:80"
+    environment:
+      BACKEND_URL: "http://HOST_IP:8081"
+volumes:
+  mongovol: {}

+ 54 - 0
compose/docker-compose.noclient.yml

@@ -0,0 +1,54 @@
+version: "3.4"
+
+volumes:
+  dnsconfig:
+  driver: local
+services:
+  mongodb:
+    image: mongo:4.2
+    ports:
+      - "27017:27017"
+    container_name: mongodb
+    volumes:
+      - mongovol:/data/db
+    restart: always
+    environment:
+      MONGO_INITDB_ROOT_USERNAME: mongoadmin
+      MONGO_INITDB_ROOT_PASSWORD: mongopass
+  netmaker:
+    container_name: netmaker
+    ports:
+      - "8081:8081"
+      - "50051:50051"
+    depends_on:
+      - mongodb
+    image: gravitl/netmaker:v0.3
+    restart: always
+    environment:
+      SERVER_HOST: "HOST_IP"
+      CLIENT_MODE: "off"
+  netmaker-ui:
+    container_name: netmaker-ui
+    depends_on:
+      - netmaker
+    image: gravitl/netmaker-ui:v0.3
+    links:
+      - "netmaker:api"
+    ports:
+      - "80:80"
+    environment:
+      BACKEND_URL: "http://HOST_IP:8081"
+  coredns:
+    depends_on:
+      - netmaker 
+    image: coredns/coredns
+    command: -conf /root/dnsconfig/Corefile
+    container_name: coredns
+    restart: always
+    ports:
+      - "53:53/udp"
+    volumes:
+      - dnsconfig:/root/dnsconfig
+volumes:
+  mongovol: {}
+  dnsconfig: {}

+ 1 - 2
docker-compose.nodns.yml → compose/docker-compose.nodns.yml

@@ -36,7 +36,6 @@ services:
     restart: always
     restart: always
     network_mode: host
     network_mode: host
     environment:
     environment:
-      CLIENT_MODE: "off"
       DNS_MODE: "off"
       DNS_MODE: "off"
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui
@@ -48,7 +47,7 @@ services:
     ports:
     ports:
       - "80:80"
       - "80:80"
     environment:
     environment:
-      BACKEND_URL: "http://your-backend:8081"
+      BACKEND_URL: "http://HOST_IP:8081"
 volumes:
 volumes:
   mongovol: {}
   mongovol: {}
   dnsconfig: {}
   dnsconfig: {}

+ 78 - 0
compose/docker-compose.reference.yml

@@ -0,0 +1,78 @@
+version: "3.4"
+
+services:
+  mongodb: # The MongoDB Instance that backs up Netmaker
+    image: mongo:4.2
+    ports:
+      - "27017:27017" # Port Mapping for MongoDB. Can be modified, but be sure to change the MONGO_PORT env var in netmaker
+    container_name: mongodb
+    volumes:
+      - mongovol:/data/db
+    restart: always
+    environment:
+      MONGO_INITDB_ROOT_USERNAME: mongoadmin # Default username. Recommend changing for production installs. You will need to set MONGO_ADMIN netmaker env var.
+      MONGO_INITDB_ROOT_PASSWORD: mongopass # Default password. Recommend changing for production installs. You will need to set MONGO_PASS netmaker env var.
+  netmaker: # The Primary Server for running Netmaker
+    privileged: true # Necessary to run sudo/root level commands on host system. Take out if not running with CLIENT_MODE=on
+    container_name: netmaker
+    depends_on:
+      - mongodb
+    image: gravitl/netmaker:v0.3
+    volumes: # Volume mounts necessary for CLIENT_MODE to control netclient, wireguard, and networking on host (except dnsconfig, which is where dns config files are stored for use by CoreDNS)
+      - ./:/local
+      - /etc/netclient:/etc/netclient
+      - dnsconfig:/root/config/dnsconfig # Netmaker writes Corefile to this location, which gets mounted by CoreDNS for DNS configuration.
+      - /usr/bin/wg:/usr/bin/wg
+      - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
+      - /run/systemd/system:/run/systemd/system
+      - /etc/systemd/system:/etc/systemd/system
+      - /sys/fs/cgroup:/sys/fs/cgroup
+    cap_add: # Necessary for CLIENT_MODE. Should be removed if turned off. 
+      - NET_ADMIN
+      - SYS_MODULE
+    restart: always
+    network_mode: host # Necessary for CLIENT_MODE. Should be removed if turned off, but then need to add port mappings
+    environment:
+      SERVER_HOST: "" # All the Docker Compose files pre-populate this with HOST_IP, which you replace as part of the install instructions. This will set both HTTP and GRPC host.
+      SERVER_HTTP_HOST: "127.0.0.1" # Overrides SERVER_HOST if set. Useful for making HTTP and GRPC available via different interfaces/networks.
+      SERVER_GRPC_HOST: "127.0.0.1" # Overrides SERVER_HOST if set. Useful for making HTTP and GRPC available via different interfaces/networks.
+      API_PORT: 8081 # The HTTP API port for Netmaker. Used for API calls / communication from front end. If changed, need to change port of BACKEND_URL for netmaker-ui.
+      GRPC_PORT: 50051 # The GRPC port for Netmaker. Used for communications from nodes.
+      MASTER_KEY: "secretkey" # The admin master key for accessing the API. Change this in any production installation.
+      CORS_ALLOWED_ORIGIN: "*" # The "allowed origin" for API requests. Change to restrict where API requests can come from.
+      REST_BACKEND: "on" # Enables the REST backend (API running on API_PORT at SERVER_HTTP_HOST). Change to "off" to turn off.
+      AGENT_BACKEND: "on" # Enables the AGENT backend (GRPC running on GRPC_PORT at SERVER_GRPC_HOST). Change to "off" to turn off.
+      CLIENT_MODE: "on" # Enables Client Mode, meaning netclient will be deployed on server and will be manageable from UI. Change to "off" to turn off.
+      DNS_MODE: "on" # Enables DNS Mode, meaning config files will be generated for CoreDNS. Note, turning "off" does not remove CoreDNS. You still need to remove CoreDNS from compose file.
+      DISABLE_REMOTE_IP_CHECK: "off" # If turned "on", Server will not set Host based on remote IP check. This is already overridden if SERVER_HOST is set. Turned "off" by default.
+      MONGO_ADMIN: "mongoadmin" # Admin user for MongoDB. Change to match above MongoDB instance
+      MONGO_PASS: "mongopass" # Admin password for MongoDB. Change to match above MongoDB instance
+      MONGO_HOST: "127.0.0.1" # Address of MongoDB. Change if necessary.
+      MONGO_PORT: "27017" # Port of MongoDB. Change if necessary.
+      MONGO_OPTS: "/?authSource=admin" # Opts to enable admin login for Mongo.
+  netmaker-ui: # The Netmaker UI Component
+    container_name: netmaker-ui
+    depends_on:
+      - netmaker
+    image: gravitl/netmaker-ui:v0.3
+    links:
+      - "netmaker:api"
+    ports:
+      - "80:80"
+    environment:
+      BACKEND_URL: "http://HOST_IP:8081" # URL where UI will send API requests. Change based on SERVER_HOST, SERVER_HTTP_HOST, and API_PORT
+      MASTER_KEY: "secretkey" # Master Key for API calls. Will be removed in v0.3.5
+  coredns: # The DNS Server. Remove this section if DNS_MODE="off"
+    depends_on:
+      - netmaker 
+    image: coredns/coredns
+    command: -conf /root/dnsconfig/Corefile # Config location for Corefile. This is the path of file which is also mounted to Netmaker for modification.
+    container_name: coredns
+    restart: always
+    ports:
+      - "53:53/udp" # Likely needs to run at port 53 for adequate nameserver usage.
+    volumes:
+      - dnsconfig:/root/dnsconfig
+volumes:
+  mongovol: {}
+  dnsconfig: {}

+ 42 - 0
compose/docker-compose.server-only.yml

@@ -0,0 +1,42 @@
+version: "3.4"
+
+volumes:
+  dnsconfig:
+  driver: local
+services:
+  mongodb:
+    image: mongo:4.2
+    ports:
+      - "27017:27017"
+    container_name: mongodb
+    volumes:
+      - mongovol:/data/db
+    restart: always
+    environment:
+      MONGO_INITDB_ROOT_USERNAME: mongoadmin
+      MONGO_INITDB_ROOT_PASSWORD: mongopass
+  netmaker:
+    container_name: netmaker
+    depends_on:
+      - mongodb
+    image: gravitl/netmaker:v0.3
+    ports:
+      - "8081:8081"
+      - "50051:50051"
+    volumes:
+      - ./:/local
+      - /etc/netclient:/etc/netclient
+      - dnsconfig:/root/config/dnsconfig
+      - /usr/bin/wg:/usr/bin/wg:ro
+      - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
+      - /run/systemd/system:/run/systemd/system
+      - /etc/systemd/system:/etc/systemd/system
+      - /sys/fs/cgroup:/sys/fs/cgroup
+    restart: always
+    environment:
+      CLIENT_MODE: "off"
+      DNS_MODE: "off"
+      SERVER_HOST: "localhost"
+volumes:
+  mongovol: {}
+  dnsconfig: {}

+ 43 - 0
compose/docker-compose.slim.yml

@@ -0,0 +1,43 @@
+version: "3.4"
+
+volumes:
+  dnsconfig:
+  driver: local
+services:
+  mongodb:
+    image: mongo:4.2
+    ports:
+      - "27017:27017"
+    container_name: mongodb
+    volumes:
+      - mongovol:/data/db
+    restart: always
+    environment:
+      MONGO_INITDB_ROOT_USERNAME: mongoadmin
+      MONGO_INITDB_ROOT_PASSWORD: mongopass
+  netmaker:
+    container_name: netmaker
+    ports:
+      - "8081:8081"
+      - "50051:50051"
+    depends_on:
+      - mongodb
+    image: gravitl/netmaker:v0.3
+    restart: always
+    environment:
+      SERVER_HOST: "HOST_IP"
+      DNS_MODE: "off"
+      CLIENT_MODE: "off"
+  netmaker-ui:
+    container_name: netmaker-ui
+    depends_on:
+      - netmaker
+    image: gravitl/netmaker-ui:v0.3
+    links:
+      - "netmaker:api"
+    ports:
+      - "80:80"
+    environment:
+      BACKEND_URL: "http://HOST_IP:8081"
+volumes:
+  mongovol: {}

+ 5 - 3
docker-compose.yml → compose/docker-compose.yml

@@ -22,16 +22,18 @@ services:
       - ./:/local
       - ./:/local
       - /etc/netclient:/etc/netclient
       - /etc/netclient:/etc/netclient
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
-      - /usr/bin/wg:/usr/bin/wg:ro
+      - /usr/bin/wg:/usr/bin/wg
       - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
       - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
       - /run/systemd/system:/run/systemd/system
       - /run/systemd/system:/run/systemd/system
       - /etc/systemd/system:/etc/systemd/system
       - /etc/systemd/system:/etc/systemd/system
       - /sys/fs/cgroup:/sys/fs/cgroup
       - /sys/fs/cgroup:/sys/fs/cgroup
-    cap_add:
+    cap_add: 
       - NET_ADMIN
       - NET_ADMIN
       - SYS_MODULE
       - SYS_MODULE
     restart: always
     restart: always
     network_mode: host
     network_mode: host
+    environment:
+      SERVER_HOST: "HOST_IP"
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui
     depends_on:
     depends_on:
@@ -42,7 +44,7 @@ services:
     ports:
     ports:
       - "80:80"
       - "80:80"
     environment:
     environment:
-      BACKEND_URL: "http://your-ip:8081"
+      BACKEND_URL: "http://HOST_IP:8081"
   coredns:
   coredns:
     depends_on:
     depends_on:
       - netmaker 
       - netmaker 

+ 15 - 0
controllers/extClientHttpController.go

@@ -276,6 +276,12 @@ func CreateExtClient(extclient models.ExtClient) error {
 		extclient.Address = newAddress
 		extclient.Address = newAddress
 	}
 	}
 
 
+        if extclient.ClientID == "" {
+                cid := StringWithCharset(7, charset)
+                clientid := "client-" + cid
+                extclient.ClientID = clientid
+        }
+
 	extclient.LastModified = time.Now().Unix()
 	extclient.LastModified = time.Now().Unix()
 
 
 	collection := mongoconn.Client.Database("netmaker").Collection("extclients")
 	collection := mongoconn.Client.Database("netmaker").Collection("extclients")
@@ -417,3 +423,12 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	returnSuccessResponse(w, r, params["clientid"]+" deleted.")
 	returnSuccessResponse(w, r, params["clientid"]+" deleted.")
 }
 }
+
+func StringWithCharset(length int, charset string) string {
+        b := make([]byte, length)
+        for i := range b {
+                b[i] = charset[seededRand.Intn(len(charset))]
+        }
+        return string(b)
+}
+

+ 20 - 0
docs/Makefile

@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

BIN
docs/_build/doctrees/about.doctree


BIN
docs/_build/doctrees/api.doctree


BIN
docs/_build/doctrees/architecture.doctree


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


BIN
docs/_build/doctrees/conduct.doctree


BIN
docs/_build/doctrees/contact.doctree


BIN
docs/_build/doctrees/contribute.doctree


BIN
docs/_build/doctrees/environment.pickle


BIN
docs/_build/doctrees/external-clients.doctree


BIN
docs/_build/doctrees/getting-started.doctree


BIN
docs/_build/doctrees/index.doctree


BIN
docs/_build/doctrees/introduction.doctree


BIN
docs/_build/doctrees/license.doctree


BIN
docs/_build/doctrees/quick-start.doctree


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


BIN
docs/_build/doctrees/support.doctree


BIN
docs/_build/doctrees/troubleshoot.doctree


BIN
docs/_build/doctrees/tutorials.doctree


BIN
docs/_build/doctrees/usage.doctree


BIN
docs/_build/doctrees/walkthroughs.doctree


+ 4 - 0
docs/_build/html/.buildinfo

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

BIN
docs/_build/html/_images/access-key.png


BIN
docs/_build/html/_images/create-user.png


BIN
docs/_build/html/_images/default-net.png


BIN
docs/_build/html/_images/mesh.png


BIN
docs/_build/html/_images/nc-install-output.png


BIN
docs/_build/html/_images/netmaker.png


BIN
docs/_build/html/_images/nm-diagram.jpg


BIN
docs/_build/html/_images/nm-node-success.png


BIN
docs/_build/html/_images/node-details.png


BIN
docs/_build/html/_images/nodes.png


BIN
docs/_build/html/_images/ping-node.png


+ 46 - 0
docs/_build/html/_sources/about.rst.txt

@@ -0,0 +1,46 @@
+===============
+About
+===============
+
+What is Netmaker?
+==================
+
+Netmaker is a tool for creating and managing virtual overlay networks. If you have at least two machines with internet access which you need to connect with a secure tunnel, Netmaker is for you. If you have thousands of servers spread across multiple locations, data centers, or clouds, Netmaker is also for you. Netmaker connects machines securely, wherever they are.
+
+.. image:: images/mesh-diagram.png
+   :width: 50%
+   :alt: WireGuard Mesh
+   :align: center
+
+Netmaker takes those machines and creates a flat network so that they can all talk to each other easily and securely. 
+If you're familiar with AWS, it's like a VPC but made up of arbitrary computers. From the machine's perspective, all these other machines are in the same neighborhood, even if they're spread all over the world.
+
+Netmaker has many similarities to Tailscale, ZeroTier, and Nebula. What makes Netmaker different is its speed and flexibility. Netmaker is faster because it uses kernel WireGuard. It is more dynamic because the server and agents are fully configurable, which lets you handle all sorts of different use cases.
+
+How Does Netmaker Work?
+=======================
+
+Netmaker relies on WireGuard to create tunnels between machines. At its core, Netmaker is managing WireGuard across machines to create sensible networks. Technically, Netmaker is two things:
+
+- the admin server, called Netmaker
+- the agent, called Netclient
+
+As the network manager, you interact with the server to create and manage networks and devices. The server holds configurations for these networks and devices, which are retrieved by the netclients (agent). 
+
+The netclient is installed on any machine you would like to add to a given network, whether that machine is a VM, Server, or IoT device. The netclient reaches out to the server, and the server tells it how it should configure the network. By doing this across many machines simultaneously, we create a dynamic, fully configurable virtual networks.
+
+The Netmaker server does not typically route traffic. Otherwise, this would be a hub-and-spoke model, which is very slow. Instead, Netmaker just tells the machines on the network how they can reach each other directly. This is called a *full mesh* network and is much faster. Even if the server goes down, as long as none of the existing machines change substantially, your network will still run just fine.
+
+Use Cases for Netmaker
+=============================
+
+There are many use cases for Netmaker. In fact, you could probably be using it right now. This list is not all-encompassing, but provides a sample of how you might want to use Netmaker. Guided setup for many of these use cases can be found in the :doc:`Using Netmaker <./usage>` documentation. 
+
+ 0. Automate creation of a WireGuard mesh network
+ 1. Create a flat, secure network between cloud environments and data centers
+ 2. Provide secure access to IoT devices, remote servers, and client sites.
+ 3. Secure a home or office network
+ 4. Add a layer of encryption to an existing network 
+ 5. Secure site-to-site connections
+ 6. Manage cryptocurrency proof-of-stake machines 
+ 7. Create a dynamic and secure Kubernetes underlay network

+ 184 - 0
docs/_build/html/_sources/api.rst.txt

@@ -0,0 +1,184 @@
+=============================================
+API Reference
+=============================================
+
+API Usage
+==========================
+
+Most actions that can be performed via API can be performed via UI. We recommend managing your networks using the official netmaker-ui project. However, Netmaker can also be run without the UI, and all functions can be achieved via API calls. If your use case requires using Netmaker without the UI or you need to do some troubleshooting/advanced configuration, using the API directly may help.
+
+
+Authentication
+==============
+API calls must be authenticated via a header of  the format  `-H "Authorization: Bearer <YOUR_SECRET_KEY>"` There are two methods to obtain YOUR_SECRET_KEY:
+1. Using the masterkey. By default, this value is "secret key," but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [general usage](./USAGE.md) documentation for more details.
+2. Using a JWT recieved for a node. This  can be retrieved by calling the `/api/nodes/<network>/authenticate` endpoint, as documented below.
+
+
+Format of Calls for Curl
+========================
+Requests take the format of `curl -H "Authorization: Bearer <YOUR_SECRET_KEY>" -H 'Content-Type: application/json' localhost:8081/api/path/to/endpoint`
+
+
+API Documentation
+=================
+
+Networks API
+------------
+
+**Get All Networks:** `/api/networks`, `GET` 
+  
+**Create Network:** `/api/network`, `POST` 
+  
+**Get Network:** `/api/networks/{network id}`, `GET`  
+  
+**Update Network:** `/api/networks/{network id}`, `PUT`  
+  
+**Delete Network:** `/api/networks/{network id}`, `DELETE`  
+  
+**Cycle PublicKeys on all Nodes:** `/api/networks/{network id}/keyupdate`, `POST`  
+  
+  
+Networks API Call Examples
+--------------------------  
+  
+**Get All Networks:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/networks | jq`
+
+**Create Network:** `curl -d '{"addressrange":"10.70.0.0/16","netid":"skynet"}' -H "Authorization: Bearer YOUR_SECRET_KEY" -H 'Content-Type: application/json' localhost:8081/api/networks`
+
+**Get Network:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/networks/skynet | jq`
+
+**Update Network:** `curl -X PUT -d '{"displayname":"my-house"}' -H "Authorization: Bearer YOUR_SECRET_KEY" -H 'Content-Type: application/json' localhost:8081/api/networks/skynet`
+
+**Delete Network:** `curl -X DELETE -H "Authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/networks/skynet`
+
+**Cycle PublicKeys on all Nodes:** `curl -X POST -H "Authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/networks/skynet/keyupdate`
+
+Access Keys API
+---------------
+
+**Get All Keys:** `/api/networks/{network id}/keys`, `GET` 
+  
+**Create Key:** `/api/networks/{network id}/keys`, `GET` 
+  
+**Delete Key:** `/api/networks/{network id}/keys/{keyname}`, `DELETE` 
+  
+  
+Access Keys API Call Examples
+-----------------------------
+   
+**Get All Keys:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/networks/skynet/keys | jq`
+  
+**Create Key:** `curl -d '{"uses":10,"name":"mykey"}' -H "Authorization: Bearer YOUR_SECRET_KEY" -H 'Content-Type: application/json' localhost:8081/api/networks/skynet/keys`
+  
+**Delete Key:** `curl -X DELETE -H "Authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/networks/skynet/keys/mykey`
+  
+    
+Nodes API
+---------
+  
+**Get All Nodes:** `/api/nodes`, `GET` 
+  
+**Get Network Nodes:** `/api/nodes/{network id}`, `GET` 
+  
+**Create Node:** `/api/nodes/{network id}`, `POST`  
+  
+**Get Node:** `/api/nodes/{network id}/{macaddress}`, `GET`  
+  
+**Update Node:** `/api/nodes/{network id}/{macaddress}`, `PUT`  
+  
+**Delete Node:** `/api/nodes/{network id}/{macaddress}`, `DELETE`  
+  
+**Check In Node:** `/api/nodes/{network id}/{macaddress}/checkin`, `POST`  
+  
+**Create a Gateway:** `/api/nodes/{network id}/{macaddress}/creategateway`, `POST`  
+  
+**Delete a Gateway:** `/api/nodes/{network id}/{macaddress}/deletegateway`, `DELETE`  
+  
+**Uncordon (Approve) a Pending Node:** `/api/nodes/{network id}/{macaddress}/uncordon`, `POST`  
+  
+**Get Last Modified Date (Last Modified Node in Network):** `/api/nodes/adm/{network id}/lastmodified`, `GET`  
+  
+**Authenticate:** `/api/nodes/adm/{network id}/authenticate`, `POST`  
+  
+  
+Nodes API Call Examples
+----------------------- 
+  
+**Get All Nodes:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" http://localhost:8081/api/nodes | jq`
+  
+**Get Network Nodes:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" http://localhost:8081/api/nodes/skynet | jq`
+    
+**Create Node:** `curl  -d  '{ "endpoint": 100.200.100.200, "publickey": aorijqalrik3ajflaqrdajhkr,"macaddress": "8c:90:b5:06:f1:d9","password": "reallysecret","localaddress": "172.16.16.1","accesskey": "aA3bVG0rnItIRXDx","listenport": 6400}' -H 'Content-Type: application/json' -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/nodes/skynet`
+    
+**Get Node:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" http://localhost:8081/api/nodes/skynet/{macaddress} | jq`  
+  
+**Update Node:** `curl -X PUT -d '{"name":"laptop1"}' -H 'Content-Type: application/json' -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/nodes/skynet/8c:90:b5:06:f1:d9`
+  
+**Delete Node:** `curl -X DELETE -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/skynet/nodes/8c:90:b5:06:f1:d9`
+  
+**Create a Gateway:** `curl  -d  '{ "rangestring": "172.31.0.0/16", "interface": "eth0"}' -H 'Content-Type: application/json' -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/nodes/skynet/8c:90:b5:06:f1:d9/creategateway`
+  
+**Delete a Gateway:** `curl -X DELETE -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/nodes/skynet/8c:90:b5:06:f1:d9/deletegateway`
+  
+**Approve a Pending Node:** `curl -X POST -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/nodes/skynet/8c:90:b5:06:f1:d9/approve`
+  
+**Get Last Modified Date (Last Modified Node in Network):** `curl -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/nodes/adm/skynet/lastmodified`
+
+**Authenticate:** `curl -d  '{"macaddress": "8c:90:b5:06:f1:d9", "password": "YOUR_PASSWORD"}' -H 'Content-Type: application/json' localhost:8081/api/nodes/adm/skynet/authenticate`
+  
+
+Users API
+-----------------------
+  
+**Note:** Only able to create Admin user at this time. The "user" is only used by the `user interface <https://github.com/gravitl/netmaker-ui>`_ to authenticate the  single  admin user.
+
+**Get User:** `/api/users/{username}`, `GET`  
+  
+**Update User:** `/api/users/{username}`, `PUT`  
+  
+**Delete User:** `/api/users/{username}`, `DELETE`  
+  
+**Check for Admin User:** `/api/users/adm/hasadmin`, `GET` 
+  
+**Create Admin User:** `/api/users/adm/createadmin`, `POST` 
+  
+**Authenticate:** `/api/users/adm/authenticate`, `POST` 
+  
+  
+Users API Calls Examples
+------------------------
+  
+**Get User:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" http://localhost:8081/api/users/{username} | jq`
+
+**Update User:** `curl -X PUT -d '{"password":"noonewillguessthis"}' -H 'Content-Type: application/json' -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/users/{username}`
+  
+**Delete User:** `curl -X DELETE -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/users/{username}`
+  
+**Check for Admin User:** `curl -H "Authorization: Bearer YOUR_SECRET_KEY" http://localhost:8081/api/users/adm/hasadmin`
+  
+**Create Admin User:** `curl -d '{ "username": "smartguy", "password": "YOUR_PASS"}' -H 'Content-Type: application/json' -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/users/adm/createadmin`
+   
+**Authenticate:** `curl -d  '{"username": "smartguy", "password": "YOUR_PASS"}' -H 'Content-Type: application/json' localhost:8081/api/nodes/adm/skynet/authenticate`
+  
+
+Server Management API
+---------------------
+
+The Server Mgmt. API allows you to add and remove the server from networks.
+
+**Add to Network:** `/api/server/addnetwork/{network id}`, `POST`  
+  
+**Remove from Network:** `/api/server/removenetwork/{network id}`, `DELETE`  
+
+**Add to Network:**  `curl -X POST -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/server/addnetwork/{network id}`
+
+**Remove from Network:** `curl -X DELETE -H "authorization: Bearer YOUR_SECRET_KEY" localhost:8081/api/server/removenetwork/{network id}`
+
+
+File Server API
+---------------
+  
+**Get File:** `/meshclient/files/{filename}`, `GET`
+  
+**Example:**  `curl localhost:8081/meshclient/files/meshclient`

+ 176 - 0
docs/_build/html/_sources/architecture.rst.txt

@@ -0,0 +1,176 @@
+===============
+Architecture
+===============
+
+.. image:: images/nm-diagram.jpg
+   :width: 45%
+   :alt: Netmaker Architecture Diagram
+   :align: center
+    
+
+*Pictured Above: A diagram of Netmaker's Architecture.*
+
+
+Core Concepts
+==============
+
+Familiarity with several core concepts will help when you encounter them later on in the documentation.
+
+WireGuard
+----------
+
+WireGuard is a relatively new but very important technology which was recently added to the Linux kernel. WireGuard creates very fast but simple encrypted tunnels between devices. From the `WireGuard <https://www.wireguard.com/>`_ website, "it might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry."
+
+Previous solutions like OpenVPN and IPSec are considerably more heavy and complex, while being less performant. All existing VPN tunnelling solutions will cause a significant increase in your network latency. WireGuard is the first to achieve near over-the-line network speeds, meaning you see no signigifant performance impact.  With the release of WireGuard, there is little reason to use any other existing tunnel encryption technology.
+
+Mesh Network
+-------------
+
+When we refer to a mesh network in these documents we are typically referring to a "full mesh."
+
+.. image:: images/mesh.png
+   :width: 33%
+   :alt: Full Mesh Network Diagram
+   :align: center
+
+
+A full `mesh network <https://www.bbc.co.uk/bitesize/guides/zr3yb82/revision/2>`_ exists where each machine is able to directly talk to every other machine on the network. For example, on your home network, behind your router, all the computers are likely given private addresses and can reach each other directly.
+
+This is in contrast to a hub-and-spoke network, where each machine must first pass its traffic through a relay server before it can reach other machines.
+
+In certain situations you may either want or need a *partial mesh* network, where only some devices can reach each other directly, and other devices must route their traffic through a relay/gateway. Netmaker can use this model in some use cases where it makes sense.
+
+Mesh networks are generally faster than other topologies, but are also more complicated to set up. WireGuard on its own gives you the means to create encrypted tunnels between devices, but it does not provide a method for setting up a full network. This is where Netmaker comes in.
+
+Netmaker
+---------
+
+Netmaker is a platform built off of WireGuard which enables users to create mesh networks between their devices. Netmaker can create both full and partial mesh networks depending on the use case.
+
+When we refer to Netmaker in aggregate, we are typically referring to Netmaker and the netclient, as well as other supporting services such as CoreDNS, MongoDB, and UI webserver.
+
+From an end user perspective, they typically interact with the Netmaker UI, or even just run the install script for the netclient on their devices. The other components run in the background invisibly. 
+
+Netmaker does a lot of work to set configurations for you, so that you don't have to. This includes things like WireGuard ports, endpoints, public IPs, keys, and peers. Netmaker works to abstract away as much of the network management as possible, so that you can just click to create a network, and click to add a machine to a network. That said, every machine (node) is different, and may require special configuration. That is why, while Netmaker sets practical default settings, everything within Netmaker is fully configurable.
+
+Node
+------
+
+A machine in a Netmaker network, which is managed by the Netclient, is referred to as a Node, as you will see in the UI. A Node can be a VM, a bare metal server, a desktop computer, an IoT device, or any other number of internet-connected machines on which the netclient is installed. A node is simply an endpoint in the network, which can send traffic to all the other nodes, and recieve traffic from all of the other nodes.
+
+SystemD
+-------
+
+SystemD is a system service manager for a wide array of Linux operating systems. Not all Linux distributions have adopted systemd, but, for better or worse, it has become a fairly common standard in the Linux world. That said, any non-Linux operating system will not have systemd, and many Linux/Unix distributionshave alternative system service managers.
+
+Netmaker's netclient, the agent which controls networking on all nodes, relies heavily on systemd as of version 0.3. This reliance is being reduced but is currently a core dependency, causing most of the limitations and incompatibilities. As Netmaker evolves, systemd will become just one of the possible service management options, allowing the netclient to be run on a wider array of devices.
+
+
+Components
+===========
+
+Netmaker consists of several core components, which are explained in high-level technical detail below.
+
+Netmaker Server
+------------------
+
+The Netmaker server is, at its core, a golang binary. Source code can be found `on GitHub <https://github.com/gravitl/netmaker>`_. The binary, by itself can be compiled for most systems. If you need to run the Netmaker server on a particular system, it likely can be made to work. In typical deployments, it is run as a Docker container. It can also be run as a systemd service as outlined in the non-docker install guide.
+
+The Netmaker server acts as an API to the front end, and as a GRPC server to the machines in the network. GRPC is much faster and more efficient than standard API calls, which increases the speed of transactions. For this reason, the Netmaker server exposes two ports: The default for the API is 8081, and the default for GRPC is 50051. Either the API or the GRPC server can be disabled on any given Netmaker instance can be disabled, allowing you to deploy two different servers for managing the API (which is largely for the admin's use) and GRPC (which is largely for the nodes' use).
+
+Most server settings are configurable via a config file, or by environment variables (which take precedence). If the server finds neither of these, it sets sensible defaults, including things like the server's reachable IP, ports, and which "modes" to run in.
+
+These modes include client mode and dns mode. Either of these can be disabled but are enabled by default. Client mode allows you to treat the Netmaker host machine (operating system) as a network Node, installing the netclient and controlling the host network. DNS mode has the server write config settings for CoreDNS, a separate component and nameserver, which picks up the config settings to manage node DNS.
+
+The Netmaker server interacts with (as of v0.3) a MongoDB instance, which holds information about nodes, networks, users, and other important data. This data is configuration data. For the most part, Netmaker serves configuration data to Nodes, telling them how they should configure themselves. The Netclient is the agent that actually does that configuration.
+
+
+Netclient
+----------------
+
+The netclient is, at its core, a golang binary. Source code can be found in the netclient folder of the Netmaker `GitHub Repository <https://github.com/gravitl/netmaker/tree/master/netclient>`_. The binary, by itself, can be compiled for most systems. However, this binary is designed to manage a certain number of Operating Systems. As of version 0.3, it requires systemd in order to manage the host system appropriately. It may be installable, and it may even make the machine a part of the mesh network, but it will not function in entirely (see Compatible Systems for more info) without systemd.
+
+The netclient is installed via a simple bash script, which pulls the latest binary and runs install command.
+
+The install command registers the machine with the Netmaker server using sensible defaults, which can be overridden with a config file or environment variables. Assuming the netclient has a valid key (or the network allows manual node signup), it will be registered in the Netmaker network, which will return configuration details about how to set up the local network. 
+
+The netclient then sets itself up in systemd, and configures WireGuard. At this point it should be part of the network.
+
+On a periodic basis (systemd timer), the netclient performs a "check in." It will authenticate with the server, and check to see if anything has changed in the network. It will also post changes about its own local configuration if there. If there has been a change, the server will return new configurations and the netclient will reconfigure the network.
+
+The check in process is what allows Netmaker to create dynamic mesh networks. As nodes are added to, removed from, and modified on the network, other nodes are notified, and make appropriate changes.
+
+
+MongoDB
+--------
+
+As of v0.3, Netmaker uses MongoDB as its database, and interacts with a MongoDB instance to store and retrieve information about nodes, networks, and users. Netmaker is rapidly evolving, and MongoDB provides a flexible database structure that accelerates development. However, MongoDB is also the heaviest component of Netmaker (high cpu/memory consumption), and is set to be replaced by a lighter-weight, SQL-based database in the future.
+
+Netmaker UI
+---------------
+
+The Netmaker UI is a ReactJS-based static website which can be run on top of standard webservers such as Apache and Nginx. Source code can be found `here <https://github.com/gravitl/netmaker-ui>`_. In a typical configuration, the Netmaker UI is run on Nginx as a Docker container.
+
+Netmaker can be used in its entirety without the UI, but the UI makes things a lot easier for most users. It has a sensible flow and layout for managing Networks, Nodes, Access Keys, and DNS.
+
+
+CoreDNS
+--------
+
+v0.3 introduced the concept of private DNS management for nodes. This requires a nameserver, and CoreDNS is the chosen nameserver. CoreDNS is lightweight and extensible. CoreDNS loads dns settings from a simple file, managed by Netmaker, and serves out DNS info for managed nodes. DNS can be tricky, and DNS management is currently only supported on a small set of devices, specifically those running systemd-resolved. However, the Netmaker CoreDNS instance can be added manually as a nameserver to other devices. DNS mode can also be turned off.
+
+Worth considering is that CoreDNS requires port 53 on the Netmaker host system, which may cause conflicts depending on your operating system. This is explained in the :doc:`Server Installation <./server-installation>` guide.
+
+
+Technical Process
+====================
+
+Below is a high level, step-by-step overview of the flow of communications within Netmaker (assuming Netmaker has already been installed):
+
+1. Admin creates a new network with a subnet, for instance 10.10.10.0/24
+2. Admin creates an access key for signing up new nodes
+3. Both of the above requests are routed to the server via an API call from the front end
+4. Admin runs the netclient install script on any given node (machine).
+5. Netclient decodes key, which contains the GRPC server location and port
+6. Netclient retrieves/sets local information, including open ports for WireGuard, public IP, and generating key pairs for peers
+7. Netclient reaches out to GRPC server with this information, authenticating via access key.
+8. Netmaker server verifies information and creates the node, setting default values for any missing information. 
+9. Timestamp is set for the network (see #16). 
+10. Netmaker returns settings as response to netclient. Some settings may be added or modified based on the network.
+11. Netclient recieves response. If successful, it takes any additional info returned from Netmaker and configures the local system/WireGuard
+12. Netclient sends another request to Netmaker's GRPC server, this time to retrieve the peers list (all other clients in the network).
+13. Netmaker sends back peers list, including current known configurations of all nodes in network.
+14. Netclient configures WireGuard with this information. At this point, the node is fully configured as a part of the network and should be able to reach the other nodes via private address.
+15. Netclient begins daemon (system timer) to run check in's with the server. It awaits changes, reporting local changes, and retrieving changes from any other nodes in the network.
+16. Other netclients on the network, upon checking in with the Netmaker server, will see that the timestamp has updated, and they will retrieve a new peers list, completing the update cycle.
+
+
+Compatible Systems for Netclient
+==================================
+
+To manage a node automatically, the Netmaker client (netclient) requires **systemd-based linux.** Compatible systems include:
+        - Fedora
+        - Ubuntu
+        - Debian
+        - Mint
+        - SUSE
+        - RHEL
+        - Raspian.
+        - Arch
+        - CentOS
+        - CoreOS
+      
+To manage DNS (optional), the node must have systemd-resolved. Systems that have this enabled include:
+        - Arch
+        - Debian
+        - Ubuntu
+        - SUSE
+
+Limitations
+===========
+
+Install limitations mostly include platform-specific limitations, such as needing systemd or systemd-resolved (see above). In addition the Netmaker platform has some additional limitations:
+
+- **Double NAT**: Netmaker is currently unable to route traffic for devices behind a "double NAT".
+- **CGNAT**: Netmaker is currently unable to route traffic for for devices behind a "carrier-grade NAT".
+- **Windows/iPhone/Android**: To reiterate the systemd limitation, Netmaker is not currently configured to support "end user" devices such as Windows desktops and phones generally. In v0.4, Netmaker will introduce external device gateways to allow this traffic (and many other sorts of devices).

+ 105 - 0
docs/_build/html/_sources/client-installation.rst.txt

@@ -0,0 +1,105 @@
+====================
+Client Installation
+====================
+
+This document tells you how to install the netclient on machines that will be a part of your Netmaker network, as well as non-compatible systems.
+
+These steps should be run after the Netmaker server has been created and a network has been designated within Netmaker.
+
+Introduction to Netclient
+===============================
+
+At its heart, the netclient is a simple CLI for managing access to various WireGuard-based networks. It manages WireGuard on the host system, so that you don't have to. Why is this necessary?
+
+If you are setting up a WireGuard-based virtual network, you must configure each machine with very specific settings, so that every machine can reach it, and it can reach every machine. Any changes to the settings of any one of these machines can break those connections. Any machine that is added, removed, or modified on the network requires reconfiguring every peer in the network. This can be very time consuming.
+
+The netmaker server holds configuration details about every machine in your network and how other machines should connect to it.
+
+The netclient agent connects to the server, pushing and pulling information when the network (or its local configuration) changes. 
+
+The netclient agent then configures WireGuard (and other network properties) locally, so that the network stays intact.
+
+Modes and System Compatibility
+==================================
+
+**Note: If you would like to connect non-Linux/Unix machines to your network such as phones and Windows desktops, please see the documentation on External Clients**
+
+The netclient can be run in a few "modes". System compatibility depends on which modes you intend to use. These modes can be mixed and matched across a network, meaning all machines do not have to run with the same "mode."
+
+CLI
+------------
+
+In its simplest form, the netclient can be treated as just a simple, manual, CLI tool, which a user can call to configure the machine. The cli can be compiled from source code to run on most systems, and has already been compiled for x86 and ARM devices.
+
+As a CLI, the netclient should function on any Linux or Unix based system that has the wireguard utility (callable with **wg**) installed.
+
+Daemon
+----------
+
+The netclient is intended to be run as a system daemon. This allows it to automatically retrieve and send updates. To do this, the netclient can install itself as a systemd service.
+
+This requires a systemd-based linux operating system.
+
+If running the netclient on a non-systemd system, it is recommended to manually configure the netclient as a daemon using whatever method is acceptable on the chosen operating system.
+
+Private DNS Management
+-----------------------
+
+To manage private DNS, the netclient relies on systemd-resolved (resolvectl). Absent this, it cannot set private DNS for the machine.
+
+A user may choose to manually set a private DNS nameserver of <netmaker server>:53. However, beware, as netmaker sets split dns, and the system must be configured properly. Otherwise, this nameserver may break your local DNS.
+
+Prerequisites
+=============
+
+**For netclient cli:** Linux/Unix with WireGuard installed (wg command available)
+
+**For netclient daemon:** Systemd Linux + WireGuard
+
+**For Private DNS management:** Resolvectl (systemd-resolved)
+
+Configuration
+===============
+
+Variable Reference
+--------------------
+
+Config File Reference
+------------------------
+
+CLI Reference
+------------------------
+
+Installation
+======================
+
+Token
+-------
+
+Access Key
+------------
+
+Manual
+---------
+
+Config File
+------------
+
+Managing Netclient
+=====================
+
+Viewing Logs
+---------------
+
+Making Updates
+----------------
+
+Adding/Removing Networks
+---------------------------
+
+Uninstalling
+---------------
+
+Troubleshooting
+-----------------
+

+ 77 - 0
docs/_build/html/_sources/conduct.rst.txt

@@ -0,0 +1,77 @@
+===============
+Code of Conduct
+===============
+
+Our Pledge
+==========
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+Our Standards
+=============
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+Our Responsibilities
+====================
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+Scope
+=====
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+Enforcement
+===========
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at [email protected]. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+Attribution
+===========
+
+This Code of Conduct is adapted from the `Contributor Covenant <https://contributor-covenant.org>`_, version 1.4,
+available `here <https://contributor-covenant.org/version/1/4>`_.
+

+ 8 - 0
docs/_build/html/_sources/contact.rst.txt

@@ -0,0 +1,8 @@
+=======
+Contact
+=======
+
+If you need help, try the discord or open a GitHub ticket.
+
+Email: [email protected]
+Discord: https://discord.gg/zRb9Vfhk8A

+ 26 - 0
docs/_build/html/_sources/contribute.rst.txt

@@ -0,0 +1,26 @@
+===========
+Contribute
+===========
+
+Submitting an Issue
+====================
+
+Submitting an Enhancement
+==========================
+
+Contributing Code
+==================
+
+Forking
+----------
+
+Building
+-----------
+
+Testing
+----------
+
+Submitting a PR
+-----------------
+
+

+ 19 - 0
docs/_build/html/_sources/external-clients.rst.txt

@@ -0,0 +1,19 @@
+================
+External Clients
+================
+
+Introduction
+===============
+
+Netmaker allows for "external clients" to reach into a network and access services via an Ingress Gateway. So what is an "external client"? An external client is any machine which cannot or should not be meshed. This can include:
+        - Phones
+        - Laptops
+        - Desktops
+
+An external client is not "managed," meaning it does not automatically pull the latest network configuration, or push changes to its configuration. Instead, it uses a generated WireGuard config file to access the designated **Ingress Gateway**, which **is** a managed server (running netclient). This server then forwards traffic to the appropriate endpoint, acting as a middle-man/relay.
+
+By using this method, you can hook any machine into a netmaker network that can run WireGuard.
+
+It is recommended to run the netclient where compatible, but for all other cases, a machine can be configured as an external client.
+
+Important to note, an external client is not **reachable** by the network, meaning the client can establish connections to other machines, but those machines cannot independently establish a connection back. The External Client method should only be used in use cases where one wishes to access resource runnin on the virtual network, and **not** for use cases where one wishes to make a resource accessible on the network. For that, use netclient.

+ 148 - 0
docs/_build/html/_sources/getting-started.rst.txt

@@ -0,0 +1,148 @@
+===============
+Getting Started
+===============
+
+Netmaker is a tool for creating and managing virtual overlay networks. If you have servers spread across multiple locations, data centers, or clouds, this platform can make life easier. Netmaker takes all those machines and puts them on a single, secure, flat network so that they can all talk to each other easily and securely. It's like a VPC but of arbitrary computers.
+
+Netmaker can be compared to and covers use cases similar to Tailscale, ZeroTier, or Nebula, but Netmaker does more than that, while being faster, more dynamic and more flexible.
+
+Netmaker uses kernel WireGuard to create encrypted tunnels between every node in your virtual network. Netmaker's `netclient` agent is self-updating and pulls any necessary changes (such as new peers) from the main server. 
+
+Use Cases
+=========
+ 1. Create a flat, secure network between multiple/hybrid cloud environments
+ 2. Integrate central and edge services
+ 3. Secure a home or office network while providing remote connectivity
+ 4. Manage cryptocurrency proof-of-stake machines
+ 6. Provide an additional layer of security on an existing network
+ 7. Encrypt Kubernetes inter-node communications
+ 8. Secure site-to-site connections
+
+
+Compatible Systems
+==================
+
+To manage a server automatically, Netmaker requires **systemd-based linux.** Compatible systems include:
+        - Fedora
+        - Ubuntu
+        - Debian
+        - Mint
+        - SUSE
+        - RHEL
+        - Raspian.
+        - Arch
+        - CentOS
+        - CoreOS
+      
+To manage DNS (optional), the server must have systemd-resolved. Systems that have this enabled include:
+        - Arch
+        - Debian
+        - Ubuntu
+        - SUSE
+
+
+In future releases, we will support other platforms such as Windows, MacOS, iOS, Android, and more. 
+
+Video Tutorials and Articles:
+
+
+
+Quick Start
+===========
+
+[Intro/Overview Video Tutorial](https://youtu.be/PWLPT320Ybo)  
+[Site-to-Site Video Tutorial](https://youtu.be/krCKBJhwwDk)  
+
+### Note about permissions
+The default installation requires special privileges on the server side, because Netmaker will control the local kernel Wireguard. This can be turned off and run in non-privileged mode if necessary (but disables some features). For more details, see the **Usage** docs.
+
+### Prereqs
+ 1. A running linux server to host Netmaker, with an IP reachable by your computers (Debian-based preferred but not required).
+ 2. Linux installed on the above server (Debian-based preferred but not required).
+ 3. Install Docker and Docker Compose if running in Docker Mode (see below).
+ 4. System dependencies installed:
+	 - Docker (if running in default Docker mode. DO NOT use snap install for docker.)
+	 - Docker Compose
+	 - Wireguard + Resolvectl (if running in default Client mode)
+
+#### CoreDNS Preparation
+v0.3 introduces CoreDNS as a private nameserver. To run CoreDNS on your server host, you must disable systemd-resolved to open port 53: 
+1. systemctl stop systemd-resolved
+2. systemctl disable systemd-resolved
+3. vim /etc/systemd/resolved.conf
+	 - uncomment **DNS=** and add 8.8.8.8 or whatever is your preference
+	 - uncomment **DNSStubListener=** and set to **"no"**
+ 4. sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
+
+
+
+### Launch Netmaker
+Note, this installs Netmaker with CoreDNS and a Netclient (privileged).  If you want to run the server non-privileged or without CoreDNS, see the advanced usage docs. 
+
+1. Clone this repo or just copy contents of "docker-compose.yml" to your Netmaker server (from prereqs).
+2. In docker-compose.yml, change BACKEND_URL to the public IP of your server.
+3. Run `sudo docker-compose up -d`
+4. Navigate to your server's IP in the browser and you should see the Netmaker UI asking to create a new admin user.
+5. Create a new admin user
+6. You are now ready to begin using Netmaker. 
+
+### Create a Network
+You can also just use the "default" network.
+1. Click "CREATE NETWORK" in the upper left of your console
+2. Enter a valid address range, e.g. 10.11.12.0/24
+3. Enter a name such as "homenet"
+4. Additional options:
+	- **Dual Stack**: Machines will recieve a private IPv6 address in addition to their IPv4 address.
+	- **Local:** Will use local address range for endpoints instead of public. Use Case: Home or Office network where most devices do not have public IP's. In this case you can create a gateway into the network after creating the Local Network.
+
+After Network creation, you can edit the network in the NETWORK DETAILS pane, modifying the address range and default options. You can also toggle on **Allow Node Signup Without Keys**, which makes the next step unnecessary, but allows anyone to create a node in your network, which will be cordoned in pending state.
+
+### Create Keys
+1. Click the "ACCESS KEYS" tab
+2. Click "ADD NEW ACCESSS KEY"
+3. Give your key a name and number of uses
+4. Several values will be displayed. Save these somewhere, as they will only be displayed once:
+	- **Access Key:** Use only in special edge cases where server connection string must be modified
+	- **Access Token:** Use on machines that already have the netclient utility
+	- **Install Command:** Use on machines that do not have the netclient utility
+
+### Install Agent:
+For machines **without** netclient, run the install command (from above): `curl -sfL https://raw.githubusercontent.com/gravitl/netmaker/v0.3/netclient-install.sh | KEY=<your access key> sh -`  
+For machines **with** netclient run the following (with access token from above): `sudo netclient -c install -t <access token>`
+For networks with **manual signup** enabled (see above), install using the network name: `sudo netclient -c install -n <network name>`
+
+### Manage Nodes
+Your machines should now be visible in the control pane. 
+**Modify nodes:** Click the pencil icon in the NODES pane to modify details like WireGuard port, address, and node name. You can also **DELETE** nodes here and they will lose network access.
+**Approve nodes:** If a node is in pending state (signed up without key), you can approve it. An icon will appear for pending nodes that need approval.
+
+**Gateway Mode:** Click the Gateway icon to enable gateway mode on a given node. A popup will allow you to choose an existing network, or enter a custom address range.
+*Example: You create a network in netmaker called Homenet. It has several machines on your home server. You create another network called Cloudnet. It has several machines in AWS. You have one server (server X) which is added to both networks. On Cloudnet, you make Server X a gateway to Homenet. Now, the cloudnet machines have access to your homenet machines. via  Server X.*
+
+*On Homenet, you add Server Y, a machine in AWS, and make it a gateway to a custom address range 172.16.0.0/16. The machines on your home network now have access to any AWS machines in that address range via Server Y*
+
+### Manage DNS
+On the DNS tab you can create custom DNS entries for a given network.
+
+ 1. All dns entries will be *postfixed* with a private TLD of the network name, for example, ".mynet"
+ 2. Default DNS is created for node name + TLD, for instance, node-c42wt.mynet. This is not editable.
+ 3. Click ADD ENTRY to add custom DNS
+	 - You can click CHOOSE NODE to direct DNS to a specific node in the network
+	 - You can also specify any custom address you would like, which can be outside the network (for instance, the IP for google.com)
+	 - Add a dns entry name, which will be postfixed with the network TLD. E.g. if you enter "privateapi.com", it will become "privateapi.com.networkname" 
+
+### Uninstalling Client
+To uninstall the client from a network: `sudo netclient -c remove -n < networkname >`
+To uninstall entirely, run the above for each network,  and then run `sudo rm -rf /etc/netclient`
+
+### Uninstralling Netmaker
+To uninstall the netmaker server, simply run `docker-compose down`
+
+#### LICENSE
+
+Netmaker's source code and all artifacts in this repository are freely available. All versions are published under the Server Side Public License (SSPL), version 1, which can be found here: [LICENSE.txt](./LICENSE.txt).
+
+#### CONTACT
+
+Email: [email protected]  
+Discord: https://discord.gg/zRb9Vfhk8A

+ 160 - 0
docs/_build/html/_sources/index.rst.txt

@@ -0,0 +1,160 @@
+.. Netmaker documentation master file, created by
+   sphinx-quickstart on Fri May 14 08:51:40 2021.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+
+.. image:: images/netmaker.png
+   :width: 100%
+   :alt: Netmaker WireGuard
+   :align: center
+
+.. role:: raw-html(raw)
+    :format: html
+
+:raw-html:`<br />`
+
+=======================================
+Welcome to the Netmaker Documentation
+=======================================
+
+
+Netmaker is a platform for creating and managing fast, secure, and dynamic virtual overlay networks using WireGuard.
+
+This documentation covers Netmaker's :doc:`installation <./server-installation>`, :doc:`usage <./usage>`, :doc:`troubleshooting <./support>`, and customization, as well as reference documents for the :doc:`API <./api>`, UI and Agent configuration. All of the `source code <https://github.com/gravitl/netmaker>`_ for Netmaker is on GitHub.
+
+
+.. :raw-html:`<br />`
+
+.. .. raw:: html
+..   :file: youtube-1.html
+
+About
+------
+A quick overview of Netmaker, explaining what it is, how it works, and why you should be using it.
+
+.. toctree::
+   :maxdepth: 2
+   
+   about
+
+Architecture
+---------------
+
+A technical overview of Netmaker, including design decisions and limitations.
+
+.. toctree::
+   :maxdepth: 2
+   
+   architecture
+
+Quick Start
+---------------
+
+A quick start guide to getting up and running with Netmaker and WireGuard as quickly as possible.
+
+.. toctree::
+   :maxdepth: 2
+
+   quick-start
+
+Server Installation
+--------------------
+
+A detailed guide to installing the Netmaker server (API, DB, UI, DNS), and configuration options.
+
+.. toctree::
+   :maxdepth: 2
+   
+   server-installation
+
+Client Installation
+--------------------
+
+A detailed guide to installing the Netmaker agent (netclient) on devices and configuration options.
+
+.. toctree::
+   :maxdepth: 2
+   
+   client-installation
+
+External Clients
+--------------------
+
+A detailed guide to give clients outside of the Netmaker network access to network resources.
+
+.. toctree::
+   :maxdepth: 2
+   
+   external-clients
+
+Guides
+----------------
+
+A handful of guides for use cases including site-to-site, Kubernetes, private DNS, and more.
+
+.. toctree::
+   :maxdepth: 2
+   
+   usage
+
+API Reference
+---------------
+
+A reference document for the Netmaker Server API, and example API calls for various use cases.
+
+**Coming Soon:** Swagger Documentation
+
+.. toctree::
+   :maxdepth: 1
+
+   api
+
+Troubleshooting
+----------------
+
+Help with common Netmaker/netclient issues.
+
+.. toctree::
+   :maxdepth: 2
+
+   troubleshoot
+
+
+Support
+----------------
+
+Where to go for help, and a FAQ.
+
+.. toctree::
+   :maxdepth: 2
+
+   support
+
+
+Contributing
+-----------------
+
+A guide on how to contribute to the Netmaker project.
+
+.. toctree:: 
+
+        contribute.rst
+
+Code of Conduct
+-----------------
+
+A statement on our expectations and pledge to the community.
+
+.. toctree:: 
+
+        conduct.rst
+
+Licensing
+---------------
+
+A link to the Netmaker license.
+
+.. toctree:: 
+
+        license.rst

+ 47 - 0
docs/_build/html/_sources/introduction.rst.txt

@@ -0,0 +1,47 @@
+===============
+Introduction
+===============
+
+Netmaker is a tool for creating and managing virtual overlay networks. If you have servers spread across multiple locations, data centers, or clouds, this platform can make life easier. Netmaker takes all those machines and puts them on a single, secure, flat network so that they can all talk to each other easily and securely. It's like a VPC but of arbitrary computers.
+
+Netmaker can be compared to and covers use cases similar to Tailscale, ZeroTier, or Nebula, but Netmaker does more than that, while being faster, more dynamic and more flexible.
+
+Netmaker uses kernel WireGuard to create encrypted tunnels between every node in your virtual network. Netmaker's `netclient` agent is self-updating and pulls any necessary changes (such as new peers) from the main server. 
+
+Use Cases
+=========
+ 1. Create a flat, secure network between multiple/hybrid cloud environments
+ 2. Integrate central and edge services
+ 3. Secure a home or office network while providing remote connectivity
+ 4. Manage cryptocurrency proof-of-stake machines
+ 6. Provide an additional layer of security on an existing network
+ 7. Encrypt Kubernetes inter-node communications
+ 8. Secure site-to-site connections
+
+
+Compatible Systems
+==================
+
+To manage a server automatically, Netmaker requires **systemd-based linux.** Compatible systems include:
+        - Fedora
+        - Ubuntu
+        - Debian
+        - Mint
+        - SUSE
+        - RHEL
+        - Raspian.
+        - Arch
+        - CentOS
+        - CoreOS
+      
+To manage DNS (optional), the server must have systemd-resolved. Systems that have this enabled include:
+        - Arch
+        - Debian
+        - Ubuntu
+        - SUSE
+
+
+In future releases, we will support other platforms such as Windows, MacOS, iOS, Android, and more. 
+
+Limitations
+===========

+ 6 - 0
docs/_build/html/_sources/license.rst.txt

@@ -0,0 +1,6 @@
+=======
+License
+=======
+
+Netmaker's source code and all artifacts in this repository are freely available. All versions are published under the Server Side Public License (SSPL), version 1, which can be found `here <https://raw.githubusercontent.com/gravitl/netmaker/master/LICENSE.txt>`_.
+

+ 137 - 0
docs/_build/html/_sources/quick-start.rst.txt

@@ -0,0 +1,137 @@
+===========
+Quick Start
+===========
+
+Introduction
+==============
+
+This is a guide to getting up and running with Netmaker as quickly as possible. 
+
+By default, Netmaker ships with DNS Mode and Client Mode enabled. However, these features require special permissions and are not necessary for a simple setup, so we are going to deploy without them. To learn more about enabling these features, check out the :doc:`installation docs <./server-installation>`.
+
+Prerequisites
+==================
+ #. A Linux server to host Netmaker, with an external IP reachable by your nodes (will be referred to as **your-host** in  document).
+ #. Docker and Docker Compose installed on the above server. Follow the official `Docker instructions <https://docs.docker.com/engine/install/>`_ for installing Docker and Docker Compose on your system.
+ #. All network nodes should be systemd-based (see Compatibility under :doc:`Architecture <./architecture>` docs)
+
+Install
+==============
+#. ``ssh root@your-host``
+#. ``wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/docker-compose.slim.yml``
+#. ``sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml``
+#. ``docker-compose up -d``
+
+Navigate to the IP address of your host in the browser. You should see the below screen. If not, please see the Quick Start section of the :doc:`troubleshooting <./support>` docs.
+
+.. image:: images/create-user.png
+   :width: 80%
+   :alt: Create User Screen
+   :align: center
+
+Setup
+=================
+
+#. Create your admin user, with a username and password.
+#. Login with your new user
+#. Examine the **default** network. Click on DEFAULT under NETWORK DETAILS
+
+.. image:: images/default-net.png
+   :width: 80%
+   :alt: Create User Screen
+   :align: center
+
+This displays information about the **default** network, which is created on server startup. You can delete this network if you do not need it, but for standard use cases this network should be enough to get started. Nodes will get an address from the network address range (ADDRESSRANGE). If the range conflicts with a pre-existing private network on your devices, you may want to change this, or make a new network instead. Nodes will also get default settings from here for unset configurations. For instance, the DEFAULTKEEPALIVE field will set the PersistenKeepAlive for nodes.
+
+To get started quickly, we can just use the existing default network.
+
+Create Key
+------------
+
+#. Click on the ACCESS KEYS tab and select the DEFAULT network.
+#. Click ADD NEW ACCESS KEY
+#. Give it a name (ex: "mykey") and a number of uses (ex: 25)
+#. Click CREATE KEY (**Important:** Do not click out of the following screen until you have saved your key details. It will appear only once.)
+#. Copy the bottom command under "Your agent install command with access token" and save it somewhere locally. E.x: ``curl -sfL https://raw.githubusercontent.com/gravitl/netmaker/v0.3/scripts/netclient-install.sh | KEY=vm3ow4thatogiwnsla3thsl3894ths sh -``
+
+.. image:: images/access-key.png
+   :width: 80%
+   :alt: Access Key Screen
+   :align: center
+
+You will use this command to install the netclient on your nodes. There are three different values for three different scenarios: 
+
+* The **Access Key** value is the secret string that will allow your node to authenticate with the Netmaker network. This can be used with existing netclient installations where additional configurations (such as setting the server IP manually) may be required. This is not typical. E.g. ``netclient -c install -k <access key> -s 1.2.3.4 -p 50052``
+* The **Access Token** value is a base64 encoded string that contains the server IP and grpc port, as well as the access key. This is decoded by the netclient and can be used with existing netclient installations like this: ``netclient -c install -t <access token>``. You should use this method for adding a network to a node that is already on a network. For instance, Node A is in the **mynet** network and now you are adding it to **default**.
+* The **install command** value is a curl command that can be run on Linux systems. It is a simple script that downloads the netclient binary and runs the install command all in one.
+  
+Networks can also be enabled to allow nodes to sign up without keys at all. In this scenario, nodes enter a "pending state" and are not permitted to join the network until an admin approves them.
+
+Deploy Nodes
+=================
+
+1. SSH to each machine 
+2. ``sudo su -``
+3. **Prerequisite Check:** Every Linux machine on which you run the netclient must have WireGuard and systemd installed
+
+  * ``which wg`` (should show wg binary present)
+  * ``pidof systemd && echo "systemd found" || echo "systemd not found"``
+
+4. Run the install command, Ex: ``curl -sfL https://raw.githubusercontent.com/gravitl/netmaker/v0.3/scripts/netclient-install.sh | KEY=vm3ow4thatogiwnsla3thsl3894ths sh -``
+
+You should get output similar to the below. The netclient retrieves local settings, submits them to the server for processing, and retrieves updated settings. Then it sets the local network configuration. For more information about this process, see the :doc:`client installation <./client-installation>` documentation. If this process failed and you do not see your node in the console (see below), then reference the :doc:`troubleshooting <./troubleshoot>` documentation.
+
+.. image:: images/nc-install-output.png
+   :width: 80%
+   :alt: Output from Netclient Install
+   :align: center
+
+
+.. image:: images/nm-node-success.png
+   :width: 80%
+   :alt: Node Success
+   :align: center
+
+
+Repeat the above steps for every machine you would like to add to your network. You can re-use the same install command so long as you do not run out of uses on your access key (after which it will be invalidated and deleted).
+
+Once installed on all nodes, you can test the connection by pinging the private address of any node from any other node.
+
+
+.. image:: images/ping-node.png
+   :width: 80%
+   :alt: Node Success
+   :align: center
+
+Manage Nodes
+===============
+
+Your machines should now be visible in the control pane. 
+
+.. image:: images/nodes.png
+   :width: 80%
+   :alt: Node Success
+   :align: center
+
+You can view/modify/delete any node by selecting it in the NODES tab. For instance, you can change the name to something more sensible like "workstation" or "api server". You can also modify network settings here, such as keys or the WireGuard port. These settings will be picked up by the node on its next check in. For more information, see Advanced Configuration in the :doc:`Using Netmaker <./usage>` docs.
+
+.. image:: images/node-details.png
+   :width: 80%
+   :alt: Node Success
+   :align: center
+
+
+
+Nodes can be added/removed/modified on the network at any time. Nodes can also be added to multiple Netmaker networks. Any changes will get picked up by any nodes on a given network, and will take aboue ~30 seconds to take effect.
+
+Uninstalling the netclient
+=============================
+
+1. To remove your nodes from the default network, run the following on each node: ``sudo netclient -c remove -n default``
+2. To remove the netclient entirely from each node, run ``sudo rm -rf /etc/netclient`` (after running the first step)
+
+Uninstralling Netmaker
+===========================
+
+To uninstall Netmaker from the server, simply run ``docker-compose down`` or ``docker-compose down --volumes`` to remove the docker volumes for a future installation.
+

+ 316 - 0
docs/_build/html/_sources/server-installation.rst.txt

@@ -0,0 +1,316 @@
+====================
+Server Installation
+====================
+
+This section outlines installing the Netmaker server, including Netmaker, Netmaker UI, MongoDB, and CoreDNS
+
+Notes on Optional Features
+============================
+
+There are a few key options to keep in mind when deploying Netmaker. All of the following options are enabled by default but can be disabled with a single flag at runtime (see Customization). In addition to these options, there are many more Customizable components which will be discussed later on and help to solve for special challenges and use cases.
+
+**Client Mode:** Client Mode enables Netmaker to control the underlying host server's Network. This can make management a bit easier, because Netmaker can be added into networks via a button click in the UI. This is especially useful for things like Gateways, and will open up additional options in future versions, for instance, allowing Netmaker to easily become a relay server.
+
+Client Mode requires many additional privileges on the host machine, since Netmaker needs to control kernel WireGuard. Because of this, if running in Client Mode, you must run with root privileges and mount many system directories to the Netmaker container. Running without Client Mode allows you to install without privilege escalation and increases the number of compatible systems substantially.
+
+**DNS Mode:** DNS Mode enables Netmaker to write configuration files for CoreDNS, which can be set as a DNS Server for nodes. DNS Mode, paired with a CoreDNS deployment, requires use of port 53. On many linux systems (such as Ubuntu), port 53 is already in use to support local DNS, via systemd-resolved. Running in DNS Mode may require making modifications on the host machine.
+
+**Agent Backend:** The Agent Backend is the GRPC server (by default running on port 50051). This port is not needed for the admin server. If your use case requires special access configuration, you can run two Netmaker instances, one for the admin server, and one for node access.
+
+**REST Backend:** Similar to the above, the REST backend runs by default on port 8081, and is used for admin API and UI access. By enabling the REST backend while disabling the Agent backend, you can separate the two functions for more restricted environments.
+
+
+System Compatibility
+====================
+
+Whether or not you run Netmaker in **Client Mode** is the main determination of system compatibility.
+
+With Client Mode **disabled**, Netmaker can be run on any system that supports Docker. This includes Windows, Mac, Linux, mainframes, and most Unix-based systems. It also requires no special privileges. Netmaker will only need ports for GRPC (50051 by default), the API (8081 by default), and CoreDNS (53, if enabled).
+
+With Client Mode **enabled** (the default), Netmaker has the same limitations as the :doc:`netclient <./client-installation>` (client networking agent), because client mode just means that the Netmaker server is also running a netclient. 
+
+This requires privileged (root) access to the host machine and multiple host directory mounts. It also requires WireGuard to be installed, and Linux with systemd installed (see :doc:`compatible systems <./architecture>` for more details).
+
+To run a non-docker installation, you are running the Netmaker binary, CoreDNS binary, MongoDB, and a web server directly on your host. This requires all the requirements for those individual components. Our guided install assumes systemd-based linux, but there are many other ways to install Netmaker's individual components onto machines that do not support Docker. 
+
+DNS Mode Prereqisite Setup
+====================================
+
+If you plan on running the server in DNS Mode, you will be deploying a CoreDNS server. We recommend binding CoreDNS to port 53 of the host system (which it will do by default). On some systems, this will conflift with existing processes. Specifically on linux systems running systemd-resolved, there may be a service consuming port 53. The below steps will disable systemd-resolved, and replace it with a generic (e.g. Google) nameserver. The following was tested on Ubuntu 20.04. This may have consequences for existing private DNS so proceed with caution:
+
+1. ``systemctl stop systemd-resolved`` 
+2. ``systemctl disable systemd-resolved`` 
+3. ``vim /etc/systemd/resolved.conf``
+    * uncomment DNS and add 8.8.8.8 or whatever reachable nameserver is your preference
+    * uncomment DNSStubListener and set to "no"
+4. ``ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf``
+
+Port 53 should now be available for CoreDNS to use.
+
+Docker Compose Install
+=======================
+
+The most simple (and recommended) way of installing Netmaker is to use one of the provided `Docker Compose files <https://github.com/gravitl/netmaker/tree/feature_v0.3.5_docs/compose>`_. Below are instructions for several different options to install Netmaker via Docker Compose, followed by an annotated reference Docker Compose in case your use case requires additional customization.
+
+Slim Install - No DNS and No Client Mode
+--------------------------------------------
+
+This is the same docker compose covered in the :doc:`quick start <./quick-start>`. It requires no special privileges and can run on any system with Docker and Docker Compose. However, it also does not have the full feature set, and lacks Client Mode and DNS Mode.
+
+**Prerequisites:**
+  * ports 80, 8081, and 50051 are not blocked by firewall
+  * ports 80, 8081, 50051, and 27017 are not in use 
+
+**Notes:** 
+  * You can still run the netclient on the host system even if Client Mode is not enabled. It will just be managed like the netclient on any other nodes, and will not be automatically managed by thhe server/UI.
+  * You can change the port mappings in the Docker Compose if the listed ports are already in use.
+
+Assuming you have Docker and Docker Compose installed, you can just run the following, replacing **< Insert your-host IP Address Here >** with your host IP (or domain):
+
+#. ``wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/docker-compose.slim.yml``
+#. ``sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml``
+#. ``docker-compose up -d``
+
+Full Install - DNS and Client Mode Enabled
+--------------------------------------------
+
+This installation gives you the fully-featured product with Client Mode and DNS Mode. 
+
+**Prerequisites:**
+  * systemd linux (Debian or Ubuntu reccommended)
+  * sudo privileges
+  * DNS Mode Prerequisite Setup (see above)
+  * WireGuard installed
+  * ports 80, 8081, 53, and 50051 are not blocked by firewall
+  * ports 80, 8081, 53, 50051, and 27017 are not in use
+
+**Notes:** 
+  * You can change the port mappings in the Docker Compose if the listed ports are already in use.
+  * You can run CoreDNS on a non-53 port, but this likely will cause issues on the client side (DNS on non-standard port). We do not recommend this and do not cover how to manage running CoreDNS on a different port for clients, which will likely have problems resolving a nameserver on a non-53 port.
+
+Assuming you have Docker and Docker Compose installed, you can just run the following, replacing **< Insert your-host IP Address Here >** with your host IP (or domain):
+
+#. ``sudo su -``
+#. ``wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/docker-compose.yml``
+#. ``sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml``
+#. ``docker-compose up -d``
+
+
+Server Only Install - UI, DNS, Client Disabled
+------------------------------------------------
+
+A "Server Only" install can be helpful for scenarios in which you do not want to run the UI. the UI is not mandatory for running a Netmaker network, but it makes the process easier. This mode also diables DNS and Client Modes, though you can add those back in if needed. There is no UI dependency on Client Mode or DNS Mode.
+
+**Prerequisites:**
+  * ports 8081 and 50051 are not blocked by firewall
+  * ports 8081, 50051, and 27017 are not in use
+
+**Notes:**
+  * You can still run the netclient on the host system even if Client Mode is not enabled. It will just be managed like the netclient on any other nodes, and will not be automatically managed by thhe server/UI.
+  * You can change the port mappings in the Docker Compose if the listed ports are already in use.
+
+Assuming you have Docker and Docker Compose installed, you can just run the following, replacing **< Insert your-host IP Address Here >** with your host IP (or domain):
+
+#. ``wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/docker-compose.server-only.yml``
+#. ``sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml``
+
+No DNS - CoreDNS Disabled, Client Enabled
+----------------------------------------------
+
+DNS Mode is currently limited to clients that can run resolvectl (systemd-resolved, see :doc:`Architecture docs <./architecture>` for more info). You may wish to disable DNS mode for various reasons. This installation option gives you the full feature set minus CoreDNS.
+
+**Prerequisites:**
+  * systemd linux (Debian or Ubuntu reccommended)
+  * sudo privileges
+  * WireGuard installed
+  * ports 80, 8081, and 50051 are not blocked by firewall
+  * ports 80, 8081, 50051, and 27017 are not in use
+
+**Notes:** 
+  * You can change the port mappings in the Docker Compose if the listed ports are already in use.
+  * If you would like to run DNS Mode, but disable it on some clients, this is also an option. See the :doc:`client installation <./client-installation>` documentation for more details.
+
+Assuming you have Docker and Docker Compose installed, you can just run the following, replacing **< Insert your-host IP Address Here >** with your host IP (or domain):
+
+#. ``wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/docker-compose.nodns.yml``
+#. ``sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml``
+
+No DNS - CoreDNS Disabled, Client Enabled
+
+No Client - DNS Enabled, Client Disabled
+---------------------------------------------
+
+You may want to provide DNS, but do not want to run the server with special privileges, in which case you can run with just Client Mode disabled. It requires no special privileges and can run on any system with Docker and Docker Compose. 
+
+**Prerequisites:**
+  * ports 80, 8081, 53, and 50051 are not blocked by firewall
+  * ports 80, 8081, 53, 50051, and 27017 are not in use
+  * DNS Mode Prerequisite Setup (see above)
+
+**Notes:** 
+  * You can still run the netclient on the host system even if Client Mode is not enabled. It will just be managed like the netclient on any other nodes, and will not be automatically managed by thhe server/UI.
+  * You can change the port mappings in the Docker Compose if the listed ports are already in use.
+
+Assuming you have Docker and Docker Compose installed, you can just run the following, replacing **< Insert your-host IP Address Here >** with your host IP (or domain):
+
+#. ``wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/docker-compose.noclient.yml``
+#. ``sed -i ‘s/HOST_IP/< Insert your-host IP Address Here >/g’ docker-compose.yml``
+#. ``docker-compose up -d``
+
+
+Reference Compose File - Annotated
+--------------------------------------
+
+All environment variables and options are enabled in this file. It is the equivalent to running the "full install" from the above section. However, all environment variables are included, and are set to the default values provided by Netmaker (if the environment variable was left unset, it would not change the installation). Comments are added to each option to show how you might use it to modify your installation.
+
+.. literalinclude:: ../compose/docker-compose.reference.yml
+  :language: YAML
+
+
+Linux Install without Docker
+=============================
+
+Most systems support Docker, but some, such as LXC, do not. In such environments, there are many options for installing Netmaker. Netmaker is available as a binary file, and there is a zip file of the Netmaker UI static HTML on GitHub. Beyond the UI and Server, you need to install MongoDB and CoreDNS (optional). 
+
+Below is a guided set of instructions for installing without Docker on Ubuntu 20.04. Depending on your system, the steps may vary.
+
+MongoDB Setup
+----------------
+1. Install MongoDB on your server:
+    * For Ubuntu: `sudo apt install -y mongodb`
+    * For more advanced installation or other operating systems, see  the `MongoDB documentation <https://docs.mongodb.com/manual/administration/install-community/>`_.
+
+2. Create a user:
+    * ``mongo admin``  
+    * > `db.createUser({ user: "mongoadmin" , pwd: "mongopass", roles: ["userAdminAnyDatabase", "dbAdminAnyDatabase", "readWriteAnyDatabase"]})`
+
+Server Setup
+-------------
+1. **Run the install script:** ``sudo curl -sfL https://raw.githubusercontent.com/gravitl/netmaker/v0.3.5/scripts/netmaker-server.sh | sh -``
+2. Check status:  ``sudo journalctl -u netmaker``
+3. If any settings are incorrect such as host or mongo credentials, change them under /etc/netmaker/config/environments/< your env >.yaml and then run ``sudo systemctl restart netmaker``
+
+UI Setup
+-----------
+
+The following uses NGinx as an http server. You may alternatively use Apache or any other web server that serves static web files.
+
+1. **Download UI asset files:** ``sudo wget -O /usr/share/nginx/html/netmaker-ui.zip https://github.com/gravitl/netmaker-ui/releases/download/latest/netmaker-ui.zip``
+2. **Unzip:** ``sudo unzip /usr/share/nginx/html/netmaker-ui.zip -d /usr/share/nginx/html``
+3. **Copy Config to Nginx:** ``sudo cp /usr/share/nginx/html/nginx.conf /etc/nginx/conf.d/default.conf``
+4. **Modify Default Config Path:** ``sudo sed -i 's/root \/var\/www\/html/root \/usr\/share\/nginx\/html/g' /etc/nginx/sites-available/default``
+5. **Change Backend URL:** ``sudo sh -c 'BACKEND_URL=http://<YOUR BACKEND API URL>:PORT /usr/share/nginx/html/generate_config_js.sh >/usr/share/nginx/html/config.js'``
+6. **Start Nginx:** ``sudo systemctl start nginx``
+
+CoreDNS Setup
+----------------
+
+Kubernetes Install
+=======================
+
+**This configuration is coming soon.** It will allow you to deploy Netmaker on a Kubernetes cluster.
+
+Configuration Reference
+=========================
+
+The "Reference Compose File" (above) explains many of these options. However, it is important to understand fundamentally how Netmaker sets its configuration:
+
+1. Defaults
+2. Config File
+3. Environment Variables
+
+Variable Description
+----------------------
+
+SERVER_HOST: 
+    **Default:** Server will perform an IP check and set automatically unless explicitly set, or DISABLE_REMOTE_IP_CHECK is set to true, in which case it defaults to 127.0.0.1
+
+    **Description:** Sets the SERVER_HTTP_HOST and SERVER_GRPC_HOST variables if they are unset. The address where traffic comes in. 
+
+SERVER_HTTP_HOST: 
+    **Default:** Equals SERVER_HOST if set, "127.0.0.1" if SERVER_HOST is unset.
+    
+    **Description:** Set to make the HTTP and GRPC functions available via different interfaces/networks.
+
+SERVER_GRPC_HOST: 
+    **Default:** Equals SERVER_HOST if set, "127.0.0.1" if SERVER_HOST is unset.
+
+    **Description:** Set to make the HTTP and GRPC functions available via different interfaces/networks.
+
+API_PORT:  
+    **Default:** 8081 
+
+    **Description:** The HTTP API port for Netmaker. Used for API calls / communication from front end.
+
+GRPC_PORT:  
+    **Default:** 50051
+
+    **Description:** The GRPC port for Netmaker. Used for communications from nodes.
+
+MASTER_KEY:  
+    **Default:** "secretkey" 
+
+    **Description:** The admin master key for accessing the API. Change this in any production installation.
+
+CORS_ALLOWED_ORIGIN:  
+    **Default:** "*"
+
+    **Description:** The "allowed origin" for API requests. Change to restrict where API requests can come from.
+
+REST_BACKEND:  
+    **Default:** "on" 
+
+    **Description:** Enables the REST backend (API running on API_PORT at SERVER_HTTP_HOST). Change to "off" to turn off.
+
+AGENT_BACKEND:  
+    **Default:** "on" 
+
+    **Description:** Enables the AGENT backend (GRPC running on GRPC_PORT at SERVER_GRPC_HOST). Change to "off" to turn off.
+
+CLIENT_MODE:  
+    **Default:** "on" 
+
+    **Description:** Enables Client Mode, meaning netclient will be deployed on server and will be manageable from UI. Change to "off" to turn off.
+
+DNS_MODE:  
+    **Default:** "on"
+
+    **Description:** Enables DNS Mode, meaning config files will be generated for CoreDNS.
+
+DISABLE_REMOTE_IP_CHECK:  
+    **Default:** "off" 
+
+    **Description:** If turned "on", Server will not set Host based on remote IP check. This is already overridden if SERVER_HOST is set. Turned "off" by default.
+
+MONGO_ADMIN:  
+    **Default:** "mongoadmin" 
+
+    **Description:** Admin user for MongoDB.
+
+MONGO_PASS:  
+    **Default:** "mongopass" 
+
+    **Description:** Admin password for MongoDB.
+
+MONGO_HOST:  
+    **Default:** "127.0.0.1"
+
+    **Description:** Address of MongoDB.
+
+MONGO_PORT:  
+    **Default:** "27017"
+
+    **Description:** Port of MongoDB.
+
+MONGO_OPTS:  
+    **Default:** "/?authSource=admin"
+
+    **Description:** Opts to enable admin login for Mongo.
+
+Config File Reference
+----------------------
+A config file may be placed under config/environments/<env-name>.yml. To read this file at runtime, provide the environment variable ENV at runtime. For instance, dev.yml paired with ENV=dev. Netmaker will load the specified Config file. This allows you to store and manage configurations for different environments. Below is a reference Config File you may use.
+
+.. literalinclude:: ../config/environments/dev.yaml
+  :language: YAML
+

+ 69 - 0
docs/_build/html/_sources/support.rst.txt

@@ -0,0 +1,69 @@
+=========
+Support
+=========
+
+FAQ
+======
+
+Does/Will Netmaker Support X Operating System?
+--------------------------------------------------
+
+Netmaker is initially available on a limited number of operating systems for good reason: Every operating system is designed differently. With a small team, we can either focus on making Netmaker do a lot on a few number of operating systems, or a little on a bunch of operating systems. We chose the first option. You can view the System Compatibility docs for more info, but in general, you should only be using Netmaker on systemd linux right now.
+
+However, as of v0.4, we will technically be able to bring any operating system into the network. This is a bit of a hack. v0.4 introduces Ingress Gateways. Think of it this way. You set up a private network. You want devices to access it. You set up a single node as an "Ingress Gateway" and generate config files for "external clients." These clients are unmanaged and unmeshed, meaning they can access the network but only via the gateway. It also means they will not automatically account for changes to the network, and the user will have to update the configs manually.
+
+This lets us immediately "support" any device which can run WireGuard, which includes most operating systems at this point including phones and Windows.
+
+As we stabilize the design and feature set of Netmaker, we will expand the operating system support for Netclient which configures dynamic, fully-meshed devices. Expect to see updates about new OS support every few weeks, until eventually the Ingress Gateway becomes unnecessary (though you will still want it for certain use cases).
+
+How do I install the Netclient on X?
+---------------------------------------
+
+As per the above, there are many unsupported operating systems. You are still welcome to try, it is just an executable binary file after all. If the system is unix-based and has kernel WireGuard installed, netclient may very well mesh the device into the network. However, the service likely will encounter problems retrieving updates.
+
+Is Netmaker a VPN like NordNPN?
+--------------------------------
+
+No. Netmaker makes Virtual Networks, which are technically VPNs, but different. It's more like a corporate VPN, or a VPC (if you're familiar with AWS).
+
+If you're looking to achieve self-hosted web browsing, with functionality similar to NordVPN, ExpressVPN, Surfshark, Tunnelbear, or Private Internet Access, this is probably not the project for you. Technically, you can accomplish this with Netmaker, but it would be a little like using a all-terrain vehicle for stock car racing.
+
+There are many good projects out there that support general internet privacy using WireGuard. Here are just a few of them:
+
+https://github.com/trailofbits/algo
+https://github.com/pivpn/pivpn
+https://github.com/subspacecloud/subspace
+https://github.com/mullvad/mullvadvpn-app
+
+Do you offer any paid support?
+---------------------------------
+
+Not at this time, but eventually we will. If you are interested, or if you are interested in sponsoring the project generally, please contact Alex Feiszli ([email protected]).
+
+Why the SSPL License?
+----------------------
+
+We thought long and hard about the license. Ultimately, we think this is the best way to support and ensure the health of the project long term. The community deserves something that is well-maintained, and in order to do that, eventually we need some financial support. We won't do that by limiting the project, but we will offer some additional support, and hosted options for things people would end up paying for anyway (relay servers, load balancing support, backups). 
+
+While SSPL is not an OSI-approved open source license, it let's people generally run the project however they want, both for private use and business use, without running into the issue of someone else monetizing the project and making it financially untenable. We are working on making the guidelines clear, and will make sure that the license does not impact the communities ability to use and modify the project.
+
+If you have concerns about the license leading to project restrictions down the road, just know that there are other paid, closed-source/closed-core options out there, so beyond not wanting to follow that path, we also don't think it's a good idea economically either. We firmly believe that having the project open is not only right, but the best option.
+
+All that said, we will re-evaluate the license on a regular basis and determine if an OSI-approved license makes more sense. It's just easier to move from SSPL to another license than vice-versa.
+
+Issues, Bugs, and Feature Requests
+=====================================
+
+Issues / Bugs
+----------------
+
+Feature Requests
+-------------------
+
+Contact
+===========
+If you need help, try the discord or open a GitHub ticket.
+
+Email: [email protected]
+
+Discord: https://discord.gg/zRb9Vfhk8A

+ 19 - 0
docs/_build/html/_sources/troubleshoot.rst.txt

@@ -0,0 +1,19 @@
+=================
+Troubleshooting
+=================
+
+Common Issues
+---------------
+
+Server
+-------
+
+UI
+----
+
+Agent
+-------
+
+CoreDNS
+--------
+

+ 17 - 0
docs/_build/html/_sources/tutorials.rst.txt

@@ -0,0 +1,17 @@
+===========
+Tutorials
+===========
+
+Members of the community have created helpful tutorials for getting started with Netmaker. Below are some selected tutorials on different topics.
+
+Video Tutorials
+===============
+* `Intro/Overview <https://youtu.be/PWLPT320Ybo>`_: Tutorial on first-time usage, setting up a mesh network.
+* `Site-to-Site Gateway <https://youtu.be/krCKBJhwwDk>`_: Tutorial on setting up site-to-site connections, allowing peers to access external networks via gateways.
+* `IPv6 and Private DNS <https://youtu.be/b4diaKWUcXI>`_: Tutorial on dual-stack IPv6 in Netmaker and Private DNS management (separate topics).
+* `Kubernetes Networking <https://youtu.be/z2jvlFVU3dw>`_: Tutorial on setting up cross-cloud Kubernetes clusters using Netmaker.
+
+
+Written Tutorials
+=================
+* `Kubernetes Networking <https://itnext.io/how-to-deploy-a-single-kubernetes-cluster-across-multiple-clouds-using-k3s-and-wireguard-a5ae176a6e81>`_: Tutorial on setting up cross-cloud Kubernetes clusters using Netmaker.

+ 39 - 0
docs/_build/html/_sources/usage.rst.txt

@@ -0,0 +1,39 @@
+==============
+Using Netmaker
+==============
+
+Netmaker has many different use cases, from a basic virtual network to an office gateway VPN to a Kubernetes underlay. It can be a bit overwhelming to figure out where to start. If you don't find your use case here, but think Netmaker is a good fit, let us know!
+
+External Tutorials
+==================
+
+Members of the community have created helpful tutorials for getting started with Netmaker. Below are some selected tutorials on different topics.
+
+Video Tutorials
+---------------
+* `Intro/Overview <https://youtu.be/PWLPT320Ybo>`_: Tutorial on first-time usage, setting up a mesh network.
+* `Site-to-Site Gateway <https://youtu.be/krCKBJhwwDk>`_: Tutorial on setting up site-to-site connections, allowing peers to access external networks via gateways.
+* `IPv6 and Private DNS <https://youtu.be/b4diaKWUcXI>`_: Tutorial on dual-stack IPv6 in Netmaker and Private DNS management (separate topics).
+* `Kubernetes Networking <https://youtu.be/z2jvlFVU3dw>`_: Tutorial on setting up cross-cloud Kubernetes clusters using Netmaker.
+
+
+Written Tutorials
+-----------------
+* `Kubernetes Cross-cloud cluster <https://itnext.io/how-to-deploy-a-single-kubernetes-cluster-across-multiple-clouds-using-k3s-and-wireguard-a5ae176a6e81>`_: Tutorial on setting up cross-cloud Kubernetes clusters using Netmaker.
+
+Basic
+=====
+
+Local Network
+=============
+  
+Site-to-Site
+============
+
+Dual Stack with IPv6
+====================
+
+Kubernetes Node Network
+========================
+
+

+ 37 - 0
docs/_build/html/_sources/walkthroughs.rst.txt

@@ -0,0 +1,37 @@
+===========
+Walkthroughs
+===========
+
+External Tutorials
+==================
+
+Members of the community have created helpful tutorials for getting started with Netmaker. Below are some selected tutorials on different topics.
+
+Video Tutorials
+---------------
+* `Intro/Overview <https://youtu.be/PWLPT320Ybo>`_: Tutorial on first-time usage, setting up a mesh network.
+* `Site-to-Site Gateway <https://youtu.be/krCKBJhwwDk>`_: Tutorial on setting up site-to-site connections, allowing peers to access external networks via gateways.
+* `IPv6 and Private DNS <https://youtu.be/b4diaKWUcXI>`_: Tutorial on dual-stack IPv6 in Netmaker and Private DNS management (separate topics).
+* `Kubernetes Networking <https://youtu.be/z2jvlFVU3dw>`_: Tutorial on setting up cross-cloud Kubernetes clusters using Netmaker.
+
+
+Written Tutorials
+-----------------
+* `Kubernetes Networking <https://itnext.io/how-to-deploy-a-single-kubernetes-cluster-across-multiple-clouds-using-k3s-and-wireguard-a5ae176a6e81>`_: Tutorial on setting up cross-cloud Kubernetes clusters using Netmaker.
+
+Basic
+=====
+
+Local Network
+=============
+  
+Site-to-Site
+============
+
+Dual Stack with IPv6
+====================
+
+Kubernetes Node Network
+========================
+
+

+ 904 - 0
docs/_build/html/_static/basic.css

@@ -0,0 +1,904 @@
+/*
+ * basic.css
+ * ~~~~~~~~~
+ *
+ * Sphinx stylesheet -- basic theme.
+ *
+ * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+    clear: both;
+}
+
+div.section::after {
+    display: block;
+    content: '';
+    clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+    width: 100%;
+    font-size: 90%;
+}
+
+div.related h3 {
+    display: none;
+}
+
+div.related ul {
+    margin: 0;
+    padding: 0 0 0 10px;
+    list-style: none;
+}
+
+div.related li {
+    display: inline;
+}
+
+div.related li.right {
+    float: right;
+    margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+    padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+    float: left;
+    width: 230px;
+    margin-left: -100%;
+    font-size: 90%;
+    word-wrap: break-word;
+    overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+    list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+    margin-left: 20px;
+    list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+    margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+    border: 1px solid #98dbcc;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+    overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+    float: left;
+    width: 80%;
+    padding: 0.25em;
+    box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+    float: left;
+    width: 20%;
+    border-left: none;
+    padding: 0.25em;
+    box-sizing: border-box;
+}
+
+
+img {
+    border: 0;
+    max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+    margin: 10px 0 0 20px;
+    padding: 0;
+}
+
+ul.search li {
+    padding: 5px 0 5px 20px;
+    background-image: url(file.png);
+    background-repeat: no-repeat;
+    background-position: 0 7px;
+}
+
+ul.search li a {
+    font-weight: bold;
+}
+
+ul.search li p.context {
+    color: #888;
+    margin: 2px 0 0 30px;
+    text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+    font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+    width: 90%;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+table.contentstable p.biglink {
+    line-height: 150%;
+}
+
+a.biglink {
+    font-size: 1.3em;
+}
+
+span.linkdescr {
+    font-style: italic;
+    padding-top: 5px;
+    font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+    width: 100%;
+}
+
+table.indextable td {
+    text-align: left;
+    vertical-align: top;
+}
+
+table.indextable ul {
+    margin-top: 0;
+    margin-bottom: 0;
+    list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+    padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+    height: 10px;
+}
+
+table.indextable tr.cap {
+    margin-top: 10px;
+    background-color: #f2f2f2;
+}
+
+img.toggler {
+    margin-right: 3px;
+    margin-top: 3px;
+    cursor: pointer;
+}
+
+div.modindex-jumpbox {
+    border-top: 1px solid #ddd;
+    border-bottom: 1px solid #ddd;
+    margin: 1em 0 1em 0;
+    padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+    border-top: 1px solid #ddd;
+    border-bottom: 1px solid #ddd;
+    margin: 1em 0 1em 0;
+    padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+    padding: 2px;
+    border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+    min-width: 450px;
+    max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+    -moz-hyphens: auto;
+    -ms-hyphens: auto;
+    -webkit-hyphens: auto;
+    hyphens: auto;
+}
+
+a.headerlink {
+    visibility: hidden;
+}
+
+a.brackets:before,
+span.brackets > a:before{
+    content: "[";
+}
+
+a.brackets:after,
+span.brackets > a:after {
+    content: "]";
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+    visibility: visible;
+}
+
+div.body p.caption {
+    text-align: inherit;
+}
+
+div.body td {
+    text-align: left;
+}
+
+.first {
+    margin-top: 0 !important;
+}
+
+p.rubric {
+    margin-top: 30px;
+    font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+    clear: left;
+    float: left;
+    margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+    clear: right;
+    float: right;
+    margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.align-left {
+    text-align: left;
+}
+
+.align-center {
+    text-align: center;
+}
+
+.align-default {
+    text-align: center;
+}
+
+.align-right {
+    text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+    margin: 0 0 0.5em 1em;
+    border: 1px solid #ddb;
+    padding: 7px;
+    background-color: #ffe;
+    width: 40%;
+    float: right;
+    clear: right;
+    overflow-x: auto;
+}
+
+p.sidebar-title {
+    font-weight: bold;
+}
+
+div.admonition, div.topic, blockquote {
+    clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+div.topic {
+    border: 1px solid #ccc;
+    padding: 7px;
+    margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+    font-size: 1.1em;
+    font-weight: bold;
+    margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    padding: 7px;
+}
+
+div.admonition dt {
+    font-weight: bold;
+}
+
+p.admonition-title {
+    margin: 0px 10px 5px 0px;
+    font-weight: bold;
+}
+
+div.body p.centered {
+    text-align: center;
+    margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+    margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+    display: block;
+    content: '';
+    clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    border: 0;
+    border-collapse: collapse;
+}
+
+table.align-center {
+    margin-left: auto;
+    margin-right: auto;
+}
+
+table.align-default {
+    margin-left: auto;
+    margin-right: auto;
+}
+
+table caption span.caption-number {
+    font-style: italic;
+}
+
+table caption span.caption-text {
+}
+
+table.docutils td, table.docutils th {
+    padding: 1px 8px 1px 5px;
+    border-top: 0;
+    border-left: 0;
+    border-right: 0;
+    border-bottom: 1px solid #aaa;
+}
+
+table.footnote td, table.footnote th {
+    border: 0 !important;
+}
+
+th {
+    text-align: left;
+    padding-right: 5px;
+}
+
+table.citation {
+    border-left: solid 1px gray;
+    margin-left: 1px;
+}
+
+table.citation td {
+    border-bottom: none;
+}
+
+th > :first-child,
+td > :first-child {
+    margin-top: 0px;
+}
+
+th > :last-child,
+td > :last-child {
+    margin-bottom: 0px;
+}
+
+/* -- figures --------------------------------------------------------------- */
+
+div.figure, figure {
+    margin: 0.5em;
+    padding: 0.5em;
+}
+
+div.figure p.caption, figcaption {
+    padding: 0.3em;
+}
+
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
+    font-style: italic;
+}
+
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
+}
+
+/* -- field list styles ----------------------------------------------------- */
+
+table.field-list td, table.field-list th {
+    border: 0 !important;
+}
+
+.field-list ul {
+    margin: 0;
+    padding-left: 1em;
+}
+
+.field-list p {
+    margin: 0;
+}
+
+.field-name {
+    -moz-hyphens: manual;
+    -ms-hyphens: manual;
+    -webkit-hyphens: manual;
+    hyphens: manual;
+}
+
+/* -- hlist styles ---------------------------------------------------------- */
+
+table.hlist {
+    margin: 1em 0;
+}
+
+table.hlist td {
+    vertical-align: top;
+}
+
+/* -- object description styles --------------------------------------------- */
+
+.sig {
+	font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+}
+
+.sig-name, code.descname {
+    background-color: transparent;
+    font-weight: bold;
+}
+
+.sig-name {
+	font-size: 1.1em;
+}
+
+code.descname {
+    font-size: 1.2em;
+}
+
+.sig-prename, code.descclassname {
+    background-color: transparent;
+}
+
+.optional {
+    font-size: 1.3em;
+}
+
+.sig-paren {
+    font-size: larger;
+}
+
+.sig-param.n {
+	font-style: italic;
+}
+
+/* C++ specific styling */
+
+.sig-inline.c-texpr,
+.sig-inline.cpp-texpr {
+	font-family: unset;
+}
+
+.sig.c   .k, .sig.c   .kt,
+.sig.cpp .k, .sig.cpp .kt {
+	color: #0033B3;
+}
+
+.sig.c   .m,
+.sig.cpp .m {
+	color: #1750EB;
+}
+
+.sig.c   .s, .sig.c   .sc,
+.sig.cpp .s, .sig.cpp .sc {
+	color: #067D17;
+}
+
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+    list-style: decimal;
+}
+
+ol.loweralpha {
+    list-style: lower-alpha;
+}
+
+ol.upperalpha {
+    list-style: upper-alpha;
+}
+
+ol.lowerroman {
+    list-style: lower-roman;
+}
+
+ol.upperroman {
+    list-style: upper-roman;
+}
+
+:not(li) > ol > li:first-child > :first-child,
+:not(li) > ul > li:first-child > :first-child {
+    margin-top: 0px;
+}
+
+:not(li) > ol > li:last-child > :last-child,
+:not(li) > ul > li:last-child > :last-child {
+    margin-bottom: 0px;
+}
+
+ol.simple ol p,
+ol.simple ul p,
+ul.simple ol p,
+ul.simple ul p {
+    margin-top: 0;
+}
+
+ol.simple > li:not(:first-child) > p,
+ul.simple > li:not(:first-child) > p {
+    margin-top: 0;
+}
+
+ol.simple p,
+ul.simple p {
+    margin-bottom: 0;
+}
+
+dl.footnote > dt,
+dl.citation > dt {
+    float: left;
+    margin-right: 0.5em;
+}
+
+dl.footnote > dd,
+dl.citation > dd {
+    margin-bottom: 0em;
+}
+
+dl.footnote > dd:after,
+dl.citation > dd:after {
+    content: "";
+    clear: both;
+}
+
+dl.field-list {
+    display: grid;
+    grid-template-columns: fit-content(30%) auto;
+}
+
+dl.field-list > dt {
+    font-weight: bold;
+    word-break: break-word;
+    padding-left: 0.5em;
+    padding-right: 5px;
+}
+
+dl.field-list > dt:after {
+    content: ":";
+}
+
+dl.field-list > dd {
+    padding-left: 0.5em;
+    margin-top: 0em;
+    margin-left: 0em;
+    margin-bottom: 0em;
+}
+
+dl {
+    margin-bottom: 15px;
+}
+
+dd > :first-child {
+    margin-top: 0px;
+}
+
+dd ul, dd table {
+    margin-bottom: 10px;
+}
+
+dd {
+    margin-top: 3px;
+    margin-bottom: 10px;
+    margin-left: 30px;
+}
+
+dl > dd:last-child,
+dl > dd:last-child > :last-child {
+    margin-bottom: 0;
+}
+
+dt:target, span.highlighted {
+    background-color: #fbe54e;
+}
+
+rect.highlighted {
+    fill: #fbe54e;
+}
+
+dl.glossary dt {
+    font-weight: bold;
+    font-size: 1.1em;
+}
+
+.versionmodified {
+    font-style: italic;
+}
+
+.system-message {
+    background-color: #fda;
+    padding: 5px;
+    border: 3px solid red;
+}
+
+.footnote:target  {
+    background-color: #ffa;
+}
+
+.line-block {
+    display: block;
+    margin-top: 1em;
+    margin-bottom: 1em;
+}
+
+.line-block .line-block {
+    margin-top: 0;
+    margin-bottom: 0;
+    margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+    font-family: sans-serif;
+}
+
+.accelerator {
+    text-decoration: underline;
+}
+
+.classifier {
+    font-style: oblique;
+}
+
+.classifier:before {
+    font-style: normal;
+    margin: 0.5em;
+    content: ":";
+}
+
+abbr, acronym {
+    border-bottom: dotted 1px;
+    cursor: help;
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+    overflow: auto;
+    overflow-y: hidden;  /* fixes display issues on Chrome browsers */
+}
+
+pre, div[class*="highlight-"] {
+    clear: both;
+}
+
+span.pre {
+    -moz-hyphens: none;
+    -ms-hyphens: none;
+    -webkit-hyphens: none;
+    hyphens: none;
+}
+
+div[class*="highlight-"] {
+    margin: 1em 0;
+}
+
+td.linenos pre {
+    border: 0;
+    background-color: transparent;
+    color: #aaa;
+}
+
+table.highlighttable {
+    display: block;
+}
+
+table.highlighttable tbody {
+    display: block;
+}
+
+table.highlighttable tr {
+    display: flex;
+}
+
+table.highlighttable td {
+    margin: 0;
+    padding: 0;
+}
+
+table.highlighttable td.linenos {
+    padding-right: 0.5em;
+}
+
+table.highlighttable td.code {
+    flex: 1;
+    overflow: hidden;
+}
+
+.highlight .hll {
+    display: block;
+}
+
+div.highlight pre,
+table.highlighttable pre {
+    margin: 0;
+}
+
+div.code-block-caption + div {
+    margin-top: 0;
+}
+
+div.code-block-caption {
+    margin-top: 1em;
+    padding: 2px 5px;
+    font-size: small;
+}
+
+div.code-block-caption code {
+    background-color: transparent;
+}
+
+table.highlighttable td.linenos,
+span.linenos,
+div.doctest > div.highlight span.gp {  /* gp: Generic.Prompt */
+  user-select: none;
+  -webkit-user-select: text; /* Safari fallback only */
+  -webkit-user-select: none; /* Chrome/Safari */
+  -moz-user-select: none; /* Firefox */
+  -ms-user-select: none; /* IE10+ */
+}
+
+div.code-block-caption span.caption-number {
+    padding: 0.1em 0.3em;
+    font-style: italic;
+}
+
+div.code-block-caption span.caption-text {
+}
+
+div.literal-block-wrapper {
+    margin: 1em 0;
+}
+
+code.xref, a code {
+    background-color: transparent;
+    font-weight: bold;
+}
+
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+    background-color: transparent;
+}
+
+.viewcode-link {
+    float: right;
+}
+
+.viewcode-back {
+    float: right;
+    font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+    margin: -1px -10px;
+    padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+    vertical-align: middle;
+}
+
+div.body div.math p {
+    text-align: center;
+}
+
+span.eqno {
+    float: right;
+}
+
+span.eqno a.headerlink {
+    position: absolute;
+    z-index: 1;
+}
+
+div.math:hover a.headerlink {
+    visibility: visible;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+    div.document,
+    div.documentwrapper,
+    div.bodywrapper {
+        margin: 0 !important;
+        width: 100%;
+    }
+
+    div.sphinxsidebar,
+    div.related,
+    div.footer,
+    #top-link {
+        display: none;
+    }
+}

+ 321 - 0
docs/_build/html/_static/doctools.js

@@ -0,0 +1,321 @@
+/*
+ * doctools.js
+ * ~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilities for all documentation.
+ *
+ * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/**
+ * select a different prefix for underscore
+ */
+$u = _.noConflict();
+
+/**
+ * make the code below compatible with browsers without
+ * an installed firebug like debugger
+if (!window.console || !console.firebug) {
+  var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
+    "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
+    "profile", "profileEnd"];
+  window.console = {};
+  for (var i = 0; i < names.length; ++i)
+    window.console[names[i]] = function() {};
+}
+ */
+
+/**
+ * small helper function to urldecode strings
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
+ */
+jQuery.urldecode = function(x) {
+  if (!x) {
+    return x
+  }
+  return decodeURIComponent(x.replace(/\+/g, ' '));
+};
+
+/**
+ * small helper function to urlencode strings
+ */
+jQuery.urlencode = encodeURIComponent;
+
+/**
+ * This function returns the parsed url parameters of the
+ * current request. Multiple values per key are supported,
+ * it will always return arrays of strings for the value parts.
+ */
+jQuery.getQueryParameters = function(s) {
+  if (typeof s === 'undefined')
+    s = document.location.search;
+  var parts = s.substr(s.indexOf('?') + 1).split('&');
+  var result = {};
+  for (var i = 0; i < parts.length; i++) {
+    var tmp = parts[i].split('=', 2);
+    var key = jQuery.urldecode(tmp[0]);
+    var value = jQuery.urldecode(tmp[1]);
+    if (key in result)
+      result[key].push(value);
+    else
+      result[key] = [value];
+  }
+  return result;
+};
+
+/**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+jQuery.fn.highlightText = function(text, className) {
+  function highlight(node, addItems) {
+    if (node.nodeType === 3) {
+      var val = node.nodeValue;
+      var pos = val.toLowerCase().indexOf(text);
+      if (pos >= 0 &&
+          !jQuery(node.parentNode).hasClass(className) &&
+          !jQuery(node.parentNode).hasClass("nohighlight")) {
+        var span;
+        var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
+        if (isInSVG) {
+          span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+        } else {
+          span = document.createElement("span");
+          span.className = className;
+        }
+        span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+        node.parentNode.insertBefore(span, node.parentNode.insertBefore(
+          document.createTextNode(val.substr(pos + text.length)),
+          node.nextSibling));
+        node.nodeValue = val.substr(0, pos);
+        if (isInSVG) {
+          var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+          var bbox = node.parentElement.getBBox();
+          rect.x.baseVal.value = bbox.x;
+          rect.y.baseVal.value = bbox.y;
+          rect.width.baseVal.value = bbox.width;
+          rect.height.baseVal.value = bbox.height;
+          rect.setAttribute('class', className);
+          addItems.push({
+              "parent": node.parentNode,
+              "target": rect});
+        }
+      }
+    }
+    else if (!jQuery(node).is("button, select, textarea")) {
+      jQuery.each(node.childNodes, function() {
+        highlight(this, addItems);
+      });
+    }
+  }
+  var addItems = [];
+  var result = this.each(function() {
+    highlight(this, addItems);
+  });
+  for (var i = 0; i < addItems.length; ++i) {
+    jQuery(addItems[i].parent).before(addItems[i].target);
+  }
+  return result;
+};
+
+/*
+ * backward compatibility for jQuery.browser
+ * This will be supported until firefox bug is fixed.
+ */
+if (!jQuery.browser) {
+  jQuery.uaMatch = function(ua) {
+    ua = ua.toLowerCase();
+
+    var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+      /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+      /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+      /(msie) ([\w.]+)/.exec(ua) ||
+      ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
+      [];
+
+    return {
+      browser: match[ 1 ] || "",
+      version: match[ 2 ] || "0"
+    };
+  };
+  jQuery.browser = {};
+  jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
+}
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+var Documentation = {
+
+  init : function() {
+    this.fixFirefoxAnchorBug();
+    this.highlightSearchWords();
+    this.initIndexTable();
+    if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
+      this.initOnKeyListeners();
+    }
+  },
+
+  /**
+   * i18n support
+   */
+  TRANSLATIONS : {},
+  PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; },
+  LOCALE : 'unknown',
+
+  // gettext and ngettext don't access this so that the functions
+  // can safely bound to a different name (_ = Documentation.gettext)
+  gettext : function(string) {
+    var translated = Documentation.TRANSLATIONS[string];
+    if (typeof translated === 'undefined')
+      return string;
+    return (typeof translated === 'string') ? translated : translated[0];
+  },
+
+  ngettext : function(singular, plural, n) {
+    var translated = Documentation.TRANSLATIONS[singular];
+    if (typeof translated === 'undefined')
+      return (n == 1) ? singular : plural;
+    return translated[Documentation.PLURALEXPR(n)];
+  },
+
+  addTranslations : function(catalog) {
+    for (var key in catalog.messages)
+      this.TRANSLATIONS[key] = catalog.messages[key];
+    this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
+    this.LOCALE = catalog.locale;
+  },
+
+  /**
+   * add context elements like header anchor links
+   */
+  addContextElements : function() {
+    $('div[id] > :header:first').each(function() {
+      $('<a class="headerlink">\u00B6</a>').
+      attr('href', '#' + this.id).
+      attr('title', _('Permalink to this headline')).
+      appendTo(this);
+    });
+    $('dt[id]').each(function() {
+      $('<a class="headerlink">\u00B6</a>').
+      attr('href', '#' + this.id).
+      attr('title', _('Permalink to this definition')).
+      appendTo(this);
+    });
+  },
+
+  /**
+   * workaround a firefox stupidity
+   * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
+   */
+  fixFirefoxAnchorBug : function() {
+    if (document.location.hash && $.browser.mozilla)
+      window.setTimeout(function() {
+        document.location.href += '';
+      }, 10);
+  },
+
+  /**
+   * highlight the search words provided in the url in the text
+   */
+  highlightSearchWords : function() {
+    var params = $.getQueryParameters();
+    var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
+    if (terms.length) {
+      var body = $('div.body');
+      if (!body.length) {
+        body = $('body');
+      }
+      window.setTimeout(function() {
+        $.each(terms, function() {
+          body.highlightText(this.toLowerCase(), 'highlighted');
+        });
+      }, 10);
+      $('<p class="highlight-link"><a href="javascript:Documentation.' +
+        'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
+          .appendTo($('#searchbox'));
+    }
+  },
+
+  /**
+   * init the domain index toggle buttons
+   */
+  initIndexTable : function() {
+    var togglers = $('img.toggler').click(function() {
+      var src = $(this).attr('src');
+      var idnum = $(this).attr('id').substr(7);
+      $('tr.cg-' + idnum).toggle();
+      if (src.substr(-9) === 'minus.png')
+        $(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
+      else
+        $(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
+    }).css('display', '');
+    if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
+        togglers.click();
+    }
+  },
+
+  /**
+   * helper function to hide the search marks again
+   */
+  hideSearchWords : function() {
+    $('#searchbox .highlight-link').fadeOut(300);
+    $('span.highlighted').removeClass('highlighted');
+  },
+
+  /**
+   * make the url absolute
+   */
+  makeURL : function(relativeURL) {
+    return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
+  },
+
+  /**
+   * get the current relative url
+   */
+  getCurrentURL : function() {
+    var path = document.location.pathname;
+    var parts = path.split(/\//);
+    $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
+      if (this === '..')
+        parts.pop();
+    });
+    var url = parts.join('/');
+    return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
+  },
+
+  initOnKeyListeners: function() {
+    $(document).keydown(function(event) {
+      var activeElementType = document.activeElement.tagName;
+      // don't navigate when in search box, textarea, dropdown or button
+      if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT'
+          && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey
+          && !event.shiftKey) {
+        switch (event.keyCode) {
+          case 37: // left
+            var prevHref = $('link[rel="prev"]').prop('href');
+            if (prevHref) {
+              window.location.href = prevHref;
+              return false;
+            }
+          case 39: // right
+            var nextHref = $('link[rel="next"]').prop('href');
+            if (nextHref) {
+              window.location.href = nextHref;
+              return false;
+            }
+        }
+      }
+    });
+  }
+};
+
+// quick alias for translations
+_ = Documentation.gettext;
+
+$(document).ready(function() {
+  Documentation.init();
+});

+ 12 - 0
docs/_build/html/_static/documentation_options.js

@@ -0,0 +1,12 @@
+var DOCUMENTATION_OPTIONS = {
+    URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
+    VERSION: '0.3.5',
+    LANGUAGE: 'None',
+    COLLAPSE_INDEX: false,
+    BUILDER: 'html',
+    FILE_SUFFIX: '.html',
+    LINK_SUFFIX: '.html',
+    HAS_SOURCE: true,
+    SOURCELINK_SUFFIX: '.txt',
+    NAVIGATION_WITH_KEYS: false
+};

BIN
docs/_build/html/_static/file.png


File diff suppressed because it is too large
+ 3 - 0
docs/_build/html/_static/fonts/font-awesome.css


+ 13 - 0
docs/_build/html/_static/fonts/material-icons.css

@@ -0,0 +1,13 @@
+/*!
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE
+ * DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+ * SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND
+ * LIMITATIONS UNDER THE LICENSE.
+ */@font-face{font-display:swap;font-family:"Material Icons";font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url("specimen/MaterialIcons-Regular.woff2") format("woff2"),url("specimen/MaterialIcons-Regular.woff") format("woff"),url("specimen/MaterialIcons-Regular.ttf") format("truetype")}

BIN
docs/_build/html/_static/fonts/specimen/FontAwesome.ttf


BIN
docs/_build/html/_static/fonts/specimen/FontAwesome.woff


BIN
docs/_build/html/_static/fonts/specimen/FontAwesome.woff2


BIN
docs/_build/html/_static/fonts/specimen/MaterialIcons-Regular.ttf


BIN
docs/_build/html/_static/fonts/specimen/MaterialIcons-Regular.woff


BIN
docs/_build/html/_static/fonts/specimen/MaterialIcons-Regular.woff2


BIN
docs/_build/html/_static/images/favicon.png


+ 1 - 0
docs/_build/html/_static/images/icons/bitbucket.1b09e088.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="352" height="448" viewBox="0 0 352 448" id="__bitbucket"><path fill="currentColor" d="M203.75 214.75q2 15.75-12.625 25.25t-27.875 1.5q-9.75-4.25-13.375-14.5t-.125-20.5 13-14.5q9-4.5 18.125-3t16 8.875 6.875 16.875zm27.75-5.25q-3.5-26.75-28.25-41T154 165.25q-15.75 7-25.125 22.125t-8.625 32.375q1 22.75 19.375 38.75t41.375 14q22.75-2 38-21t12.5-42zM291.25 74q-5-6.75-14-11.125t-14.5-5.5T245 54.25q-72.75-11.75-141.5.5-10.75 1.75-16.5 3t-13.75 5.5T60.75 74q7.5 7 19 11.375t18.375 5.5T120 93.75Q177 101 232 94q15.75-2 22.375-3t18.125-5.375T291.25 74zm14.25 258.75q-2 6.5-3.875 19.125t-3.5 21-7.125 17.5-14.5 14.125q-21.5 12-47.375 17.875t-50.5 5.5-50.375-4.625q-11.5-2-20.375-4.5T88.75 412 70.5 401.125t-13-15.375q-6.25-24-14.25-73l1.5-4 4.5-2.25q55.75 37 126.625 37t126.875-37q5.25 1.5 6 5.75t-1.25 11.25-2 9.25zM350.75 92.5q-6.5 41.75-27.75 163.75-1.25 7.5-6.75 14t-10.875 10T291.75 288q-63 31.5-152.5 22-62-6.75-98.5-34.75-3.75-3-6.375-6.625t-4.25-8.75-2.25-8.5-1.5-9.875T25 232.75q-2.25-12.5-6.625-37.5t-7-40.375T5.5 118 0 78.5Q.75 72 4.375 66.375T12.25 57t11.25-7.5T35 43.875t12-4.625q31.25-11.5 78.25-16 94.75-9.25 169 12.5Q333 47.25 348 66.25q4 5 4.125 12.75t-1.375 13.5z"/></svg>

+ 1 - 0
docs/_build/html/_static/images/icons/bitbucket.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="352" height="448" viewBox="0 0 352 448" id="__bitbucket"><path fill="currentColor" d="M203.75 214.75q2 15.75-12.625 25.25t-27.875 1.5q-9.75-4.25-13.375-14.5t-.125-20.5 13-14.5q9-4.5 18.125-3t16 8.875 6.875 16.875zm27.75-5.25q-3.5-26.75-28.25-41T154 165.25q-15.75 7-25.125 22.125t-8.625 32.375q1 22.75 19.375 38.75t41.375 14q22.75-2 38-21t12.5-42zM291.25 74q-5-6.75-14-11.125t-14.5-5.5T245 54.25q-72.75-11.75-141.5.5-10.75 1.75-16.5 3t-13.75 5.5T60.75 74q7.5 7 19 11.375t18.375 5.5T120 93.75Q177 101 232 94q15.75-2 22.375-3t18.125-5.375T291.25 74zm14.25 258.75q-2 6.5-3.875 19.125t-3.5 21-7.125 17.5-14.5 14.125q-21.5 12-47.375 17.875t-50.5 5.5-50.375-4.625q-11.5-2-20.375-4.5T88.75 412 70.5 401.125t-13-15.375q-6.25-24-14.25-73l1.5-4 4.5-2.25q55.75 37 126.625 37t126.875-37q5.25 1.5 6 5.75t-1.25 11.25-2 9.25zM350.75 92.5q-6.5 41.75-27.75 163.75-1.25 7.5-6.75 14t-10.875 10T291.75 288q-63 31.5-152.5 22-62-6.75-98.5-34.75-3.75-3-6.375-6.625t-4.25-8.75-2.25-8.5-1.5-9.875T25 232.75q-2.25-12.5-6.625-37.5t-7-40.375T5.5 118 0 78.5Q.75 72 4.375 66.375T12.25 57t11.25-7.5T35 43.875t12-4.625q31.25-11.5 78.25-16 94.75-9.25 169 12.5Q333 47.25 348 66.25q4 5 4.125 12.75t-1.375 13.5z"/></svg>

+ 1 - 0
docs/_build/html/_static/images/icons/github.f0b8504a.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448" viewBox="0 0 416 448" id="__github"><path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19T128 352t-18.125-8.5-10.75-19T96 304t3.125-20.5 10.75-19T128 256t18.125 8.5 10.75 19T160 304zm160 0q0 10-3.125 20.5t-10.75 19T288 352t-18.125-8.5-10.75-19T256 304t3.125-20.5 10.75-19T288 256t18.125 8.5 10.75 19T320 304zm40 0q0-30-17.25-51T296 232q-10.25 0-48.75 5.25Q229.5 240 208 240t-39.25-2.75Q130.75 232 120 232q-29.5 0-46.75 21T56 304q0 22 8 38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0 37.25-1.75t35-7.375 30.5-15 20.25-25.75T360 304zm56-44q0 51.75-15.25 82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5T212 416q-19.5 0-35.5-.75t-36.875-3.125-38.125-7.5-34.25-12.875T37 371.5t-21.5-28.75Q0 312 0 260q0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25 30.875Q171.5 96 212 96q37 0 70 8 26.25-20.5 46.75-30.25T376 64q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34 99.5z"/></svg>

+ 1 - 0
docs/_build/html/_static/images/icons/github.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448" viewBox="0 0 416 448" id="__github"><path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19T128 352t-18.125-8.5-10.75-19T96 304t3.125-20.5 10.75-19T128 256t18.125 8.5 10.75 19T160 304zm160 0q0 10-3.125 20.5t-10.75 19T288 352t-18.125-8.5-10.75-19T256 304t3.125-20.5 10.75-19T288 256t18.125 8.5 10.75 19T320 304zm40 0q0-30-17.25-51T296 232q-10.25 0-48.75 5.25Q229.5 240 208 240t-39.25-2.75Q130.75 232 120 232q-29.5 0-46.75 21T56 304q0 22 8 38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0 37.25-1.75t35-7.375 30.5-15 20.25-25.75T360 304zm56-44q0 51.75-15.25 82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5T212 416q-19.5 0-35.5-.75t-36.875-3.125-38.125-7.5-34.25-12.875T37 371.5t-21.5-28.75Q0 312 0 260q0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25 30.875Q171.5 96 212 96q37 0 70 8 26.25-20.5 46.75-30.25T376 64q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34 99.5z"/></svg>

+ 1 - 0
docs/_build/html/_static/images/icons/gitlab.6dd19c00.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 500 500" id="__gitlab"><path fill="currentColor" d="M93.667 473.347l90.684-279.097H2.983l90.684 279.097z" transform="translate(156.198 1.16)"/><path fill="currentColor" d="M221.333 473.345L130.649 194.25H3.557l217.776 279.095z" transform="translate(28.531 1.16)" opacity=".7"/><path fill="currentColor" d="M32 195.155L4.441 279.97a18.773 18.773 0 0 0 6.821 20.99l238.514 173.29L32 195.155z" transform="translate(.089 .256)" opacity=".5"/><path fill="currentColor" d="M2.667-84.844h127.092L75.14-252.942c-2.811-8.649-15.047-8.649-17.856 0L2.667-84.844z" transform="translate(29.422 280.256)"/><path fill="currentColor" d="M2.667 473.345L93.351 194.25h127.092L2.667 473.345z" transform="translate(247.198 1.16)" opacity=".7"/><path fill="currentColor" d="M221.334 195.155l27.559 84.815a18.772 18.772 0 0 1-6.821 20.99L3.557 474.25l217.777-279.095z" transform="translate(246.307 .256)" opacity=".5"/><path fill="currentColor" d="M130.667-84.844H3.575l54.618-168.098c2.811-8.649 15.047-8.649 17.856 0l54.618 168.098z" transform="translate(336.974 280.256)"/></svg>

+ 1 - 0
docs/_build/html/_static/images/icons/gitlab.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 500 500" id="__gitlab"><path fill="currentColor" d="M93.667 473.347l90.684-279.097H2.983l90.684 279.097z" transform="translate(156.198 1.16)"/><path fill="currentColor" d="M221.333 473.345L130.649 194.25H3.557l217.776 279.095z" transform="translate(28.531 1.16)" opacity=".7"/><path fill="currentColor" d="M32 195.155L4.441 279.97a18.773 18.773 0 0 0 6.821 20.99l238.514 173.29L32 195.155z" transform="translate(.089 .256)" opacity=".5"/><path fill="currentColor" d="M2.667-84.844h127.092L75.14-252.942c-2.811-8.649-15.047-8.649-17.856 0L2.667-84.844z" transform="translate(29.422 280.256)"/><path fill="currentColor" d="M2.667 473.345L93.351 194.25h127.092L2.667 473.345z" transform="translate(247.198 1.16)" opacity=".7"/><path fill="currentColor" d="M221.334 195.155l27.559 84.815a18.772 18.772 0 0 1-6.821 20.99L3.557 474.25l217.777-279.095z" transform="translate(246.307 .256)" opacity=".5"/><path fill="currentColor" d="M130.667-84.844H3.575l54.618-168.098c2.811-8.649 15.047-8.649 17.856 0l54.618 168.098z" transform="translate(336.974 280.256)"/></svg>

+ 2540 - 0
docs/_build/html/_static/javascripts/application.js

@@ -0,0 +1,2540 @@
+! function(e, t) {
+    for (var n in t) e[n] = t[n]
+}(window, function(n) {
+    var r = {};
+
+    function i(e) {
+        if (r[e]) return r[e].exports;
+        var t = r[e] = {
+            i: e,
+            l: !1,
+            exports: {}
+        };
+        return n[e].call(t.exports, t, t.exports, i), t.l = !0, t.exports
+    }
+    return i.m = n, i.c = r, i.d = function(e, t, n) {
+        i.o(e, t) || Object.defineProperty(e, t, {
+            enumerable: !0,
+            get: n
+        })
+    }, i.r = function(e) {
+        "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
+            value: "Module"
+        }), Object.defineProperty(e, "__esModule", {
+            value: !0
+        })
+    }, i.t = function(t, e) {
+        if (1 & e && (t = i(t)), 8 & e) return t;
+        if (4 & e && "object" == typeof t && t && t.__esModule) return t;
+        var n = Object.create(null);
+        if (i.r(n), Object.defineProperty(n, "default", {
+                enumerable: !0,
+                value: t
+            }), 2 & e && "string" != typeof t)
+            for (var r in t) i.d(n, r, function(e) {
+                return t[e]
+            }.bind(null, r));
+        return n
+    }, i.n = function(e) {
+        var t = e && e.__esModule ? function() {
+            return e.default
+        } : function() {
+            return e
+        };
+        return i.d(t, "a", t), t
+    }, i.o = function(e, t) {
+        return Object.prototype.hasOwnProperty.call(e, t)
+    }, i.p = "", i(i.s = 13)
+}([function(e, t, n) {
+    "use strict";
+    var r = {
+            Listener: function() {
+                function e(e, t, n) {
+                    var r = this;
+                    this.els_ = Array.prototype.slice.call("string" == typeof e ? document.querySelectorAll(e) : [].concat(e)), this.handler_ = "function" == typeof n ? {
+                        update: n
+                    } : n, this.events_ = [].concat(t), this.update_ = function(e) {
+                        return r.handler_.update(e)
+                    }
+                }
+                var t = e.prototype;
+                return t.listen = function() {
+                    var n = this;
+                    this.els_.forEach(function(t) {
+                        n.events_.forEach(function(e) {
+                            t.addEventListener(e, n.update_, !1)
+                        })
+                    }), "function" == typeof this.handler_.setup && this.handler_.setup()
+                }, t.unlisten = function() {
+                    var n = this;
+                    this.els_.forEach(function(t) {
+                        n.events_.forEach(function(e) {
+                            t.removeEventListener(e, n.update_)
+                        })
+                    }), "function" == typeof this.handler_.reset && this.handler_.reset()
+                }, e
+            }(),
+            MatchMedia: function(e, t) {
+                this.handler_ = function(e) {
+                    e.matches ? t.listen() : t.unlisten()
+                };
+                var n = window.matchMedia(e);
+                n.addListener(this.handler_), this.handler_(n)
+            }
+        },
+        i = {
+            Shadow: function() {
+                function e(e, t) {
+                    var n = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(n instanceof HTMLElement && n.parentNode instanceof HTMLElement)) throw new ReferenceError;
+                    if (this.el_ = n.parentNode, !((n = "string" == typeof t ? document.querySelector(t) : t) instanceof HTMLElement)) throw new ReferenceError;
+                    this.header_ = n, this.height_ = 0, this.active_ = !1
+                }
+                var t = e.prototype;
+                return t.setup = function() {
+                    for (var e = this.el_; e = e.previousElementSibling;) {
+                        if (!(e instanceof HTMLElement)) throw new ReferenceError;
+                        this.height_ += e.offsetHeight
+                    }
+                    this.update()
+                }, t.update = function(e) {
+                    if (!e || "resize" !== e.type && "orientationchange" !== e.type) {
+                        var t = window.pageYOffset >= this.height_;
+                        t !== this.active_ && (this.header_.dataset.mdState = (this.active_ = t) ? "shadow" : "")
+                    } else this.height_ = 0, this.setup()
+                }, t.reset = function() {
+                    this.header_.dataset.mdState = "", this.height_ = 0, this.active_ = !1
+                }, e
+            }(),
+            Title: function() {
+                function e(e, t) {
+                    var n = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(n instanceof HTMLElement)) throw new ReferenceError;
+                    if (this.el_ = n, !((n = "string" == typeof t ? document.querySelector(t) : t) instanceof HTMLHeadingElement)) throw new ReferenceError;
+                    this.header_ = n, this.active_ = !1
+                }
+                var t = e.prototype;
+                return t.setup = function() {
+                    var t = this;
+                    Array.prototype.forEach.call(this.el_.children, function(e) {
+                        e.style.width = t.el_.offsetWidth - 20 + "px"
+                    })
+                }, t.update = function(e) {
+                    var t = this,
+                        n = window.pageYOffset >= this.header_.offsetTop;
+                    n !== this.active_ && (this.el_.dataset.mdState = (this.active_ = n) ? "active" : ""), "resize" !== e.type && "orientationchange" !== e.type || Array.prototype.forEach.call(this.el_.children, function(e) {
+                        e.style.width = t.el_.offsetWidth - 20 + "px"
+                    })
+                }, t.reset = function() {
+                    this.el_.dataset.mdState = "", this.el_.style.width = "", this.active_ = !1
+                }, e
+            }()
+        },
+        o = {
+            Blur: function() {
+                function e(e) {
+                    this.els_ = "string" == typeof e ? document.querySelectorAll(e) : e, this.index_ = 0, this.offset_ = window.pageYOffset, this.dir_ = !1, this.anchors_ = [].reduce.call(this.els_, function(e, t) {
+                        var n = decodeURIComponent(t.hash);
+                        return e.concat(document.getElementById(n.substring(1)) || [])
+                    }, [])
+                }
+                var t = e.prototype;
+                return t.setup = function() {
+                    this.update()
+                }, t.update = function() {
+                    var e = window.pageYOffset,
+                        t = this.offset_ - e < 0;
+                    if (this.dir_ !== t && (this.index_ = this.index_ = t ? 0 : this.els_.length - 1), 0 !== this.anchors_.length) {
+                        if (this.offset_ <= e)
+                            for (var n = this.index_ + 1; n < this.els_.length && this.anchors_[n].offsetTop - 80 <= e; n++) 0 < n && (this.els_[n - 1].dataset.mdState = "blur"), this.index_ = n;
+                        else
+                            for (var r = this.index_; 0 <= r; r--) {
+                                if (!(this.anchors_[r].offsetTop - 80 > e)) {
+                                    this.index_ = r;
+                                    break
+                                }
+                                0 < r && (this.els_[r - 1].dataset.mdState = "")
+                            }
+                        this.offset_ = e, this.dir_ = t
+                    }
+                }, t.reset = function() {
+                    Array.prototype.forEach.call(this.els_, function(e) {
+                        e.dataset.mdState = ""
+                    }), this.index_ = 0, this.offset_ = window.pageYOffset
+                }, e
+            }(),
+            Collapse: function() {
+                function e(e) {
+                    var t = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                    this.el_ = t
+                }
+                var t = e.prototype;
+                return t.setup = function() {
+                    var e = this.el_.getBoundingClientRect().height;
+                    this.el_.style.display = e ? "block" : "none", this.el_.style.overflow = e ? "visible" : "hidden"
+                }, t.update = function() {
+                    var e = this,
+                        t = this.el_.getBoundingClientRect().height;
+                    this.el_.style.display = "block", this.el_.style.overflow = "";
+                    var r = this.el_.previousElementSibling.previousElementSibling.checked;
+                    if (r) this.el_.style.maxHeight = t + "px", requestAnimationFrame(function() {
+                        e.el_.setAttribute("data-md-state", "animate"), e.el_.style.maxHeight = "0px"
+                    });
+                    else {
+                        this.el_.setAttribute("data-md-state", "expand"), this.el_.style.maxHeight = "";
+                        var n = this.el_.getBoundingClientRect().height;
+                        this.el_.removeAttribute("data-md-state"), this.el_.style.maxHeight = "0px", requestAnimationFrame(function() {
+                            e.el_.setAttribute("data-md-state", "animate"), e.el_.style.maxHeight = n + "px"
+                        })
+                    }
+                    this.el_.addEventListener("transitionend", function e(t) {
+                        var n = t.target;
+                        if (!(n instanceof HTMLElement)) throw new ReferenceError;
+                        n.removeAttribute("data-md-state"), n.style.maxHeight = "", n.style.display = r ? "none" : "block", n.style.overflow = r ? "hidden" : "visible", n.removeEventListener("transitionend", e)
+                    }, !1)
+                }, t.reset = function() {
+                    this.el_.dataset.mdState = "", this.el_.style.maxHeight = "", this.el_.style.display = "", this.el_.style.overflow = ""
+                }, e
+            }(),
+            Scrolling: function() {
+                function e(e) {
+                    var t = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                    this.el_ = t
+                }
+                var t = e.prototype;
+                return t.setup = function() {
+                    this.el_.children[this.el_.children.length - 1].style.webkitOverflowScrolling = "touch";
+                    var e = this.el_.querySelectorAll("[data-md-toggle]");
+                    Array.prototype.forEach.call(e, function(e) {
+                        if (!(e instanceof HTMLInputElement)) throw new ReferenceError;
+                        if (e.checked) {
+                            var t = e.nextElementSibling;
+                            if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                            for (;
+                                "NAV" !== t.tagName && t.nextElementSibling;) t = t.nextElementSibling;
+                            if (!(e.parentNode instanceof HTMLElement && e.parentNode.parentNode instanceof HTMLElement)) throw new ReferenceError;
+                            var n = e.parentNode.parentNode,
+                                r = t.children[t.children.length - 1];
+                            n.style.webkitOverflowScrolling = "", r.style.webkitOverflowScrolling = "touch"
+                        }
+                    })
+                }, t.update = function(e) {
+                    var t = e.target;
+                    if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                    var n = t.nextElementSibling;
+                    if (!(n instanceof HTMLElement)) throw new ReferenceError;
+                    for (;
+                        "NAV" !== n.tagName && n.nextElementSibling;) n = n.nextElementSibling;
+                    if (!(t.parentNode instanceof HTMLElement && t.parentNode.parentNode instanceof HTMLElement)) throw new ReferenceError;
+                    var r = t.parentNode.parentNode,
+                        i = n.children[n.children.length - 1];
+                    if (r.style.webkitOverflowScrolling = "", i.style.webkitOverflowScrolling = "", !t.checked) {
+                        n.addEventListener("transitionend", function e() {
+                            n instanceof HTMLElement && (r.style.webkitOverflowScrolling = "touch", n.removeEventListener("transitionend", e))
+                        }, !1)
+                    }
+                    if (t.checked) {
+                        n.addEventListener("transitionend", function e() {
+                            n instanceof HTMLElement && (i.style.webkitOverflowScrolling = "touch", n.removeEventListener("transitionend", e))
+                        }, !1)
+                    }
+                }, t.reset = function() {
+                    this.el_.children[1].style.webkitOverflowScrolling = "";
+                    var e = this.el_.querySelectorAll("[data-md-toggle]");
+                    Array.prototype.forEach.call(e, function(e) {
+                        if (!(e instanceof HTMLInputElement)) throw new ReferenceError;
+                        if (e.checked) {
+                            var t = e.nextElementSibling;
+                            if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                            for (;
+                                "NAV" !== t.tagName && t.nextElementSibling;) t = t.nextElementSibling;
+                            if (!(e.parentNode instanceof HTMLElement && e.parentNode.parentNode instanceof HTMLElement)) throw new ReferenceError;
+                            var n = e.parentNode.parentNode,
+                                r = t.children[t.children.length - 1];
+                            n.style.webkitOverflowScrolling = "", r.style.webkitOverflowScrolling = ""
+                        }
+                    })
+                }, e
+            }()
+        },
+        a = {
+            Lock: function() {
+                function e(e) {
+                    var t = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(t instanceof HTMLInputElement)) throw new ReferenceError;
+                    if (this.el_ = t, !document.body) throw new ReferenceError;
+                    this.lock_ = document.body
+                }
+                var t = e.prototype;
+                return t.setup = function() {
+                    this.update()
+                }, t.update = function() {
+                    var e = this;
+                    this.el_.checked ? (this.offset_ = window.pageYOffset, setTimeout(function() {
+                        window.scrollTo(0, 0), e.el_.checked && (e.lock_.dataset.mdState = "lock")
+                    }, 400)) : (this.lock_.dataset.mdState = "", setTimeout(function() {
+                        void 0 !== e.offset_ && window.scrollTo(0, e.offset_)
+                    }, 100))
+                }, t.reset = function() {
+                    "lock" === this.lock_.dataset.mdState && window.scrollTo(0, this.offset_), this.lock_.dataset.mdState = ""
+                }, e
+            }(),
+            Result: n(9).a
+        },
+        s = {
+            Position: function() {
+                function e(e, t) {
+                    var n = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(n instanceof HTMLElement && n.parentNode instanceof HTMLElement)) throw new ReferenceError;
+                    if (this.el_ = n, this.parent_ = n.parentNode, !((n = "string" == typeof t ? document.querySelector(t) : t) instanceof HTMLElement)) throw new ReferenceError;
+                    this.header_ = n, this.height_ = 0, this.pad_ = "fixed" === window.getComputedStyle(this.header_).position
+                }
+                var t = e.prototype;
+                return t.setup = function() {
+                    var e = Array.prototype.reduce.call(this.parent_.children, function(e, t) {
+                        return Math.max(e, t.offsetTop)
+                    }, 0);
+                    this.offset_ = e - (this.pad_ ? this.header_.offsetHeight : 0), this.update()
+                }, t.update = function(e) {
+                    var t = window.pageYOffset,
+                        n = window.innerHeight;
+                    e && "resize" === e.type && this.setup();
+                    var r = this.pad_ ? this.header_.offsetHeight : 0,
+                        i = this.parent_.offsetTop + this.parent_.offsetHeight,
+                        o = n - r - Math.max(0, this.offset_ - t) - Math.max(0, t + n - i);
+                    o !== this.height_ && (this.el_.style.height = (this.height_ = o) + "px"), t >= this.offset_ ? "lock" !== this.el_.dataset.mdState && (this.el_.dataset.mdState = "lock") : "lock" === this.el_.dataset.mdState && (this.el_.dataset.mdState = "")
+                }, t.reset = function() {
+                    this.el_.dataset.mdState = "", this.el_.style.height = "", this.height_ = 0
+                }, e
+            }()
+        },
+        c = n(6),
+        l = n.n(c);
+    var u = {
+            Adapter: {
+                GitHub: function(o) {
+                    var e, t;
+
+                    function n(e) {
+                        var t;
+                        t = o.call(this, e) || this;
+                        var n = /^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);
+                        if (n && 3 === n.length) {
+                            var r = n[1],
+                                i = n[2];
+                            t.base_ = "https://api.github.com/users/" + r + "/repos", t.name_ = i
+                        }
+                        return t
+                    }
+                    return t = o, (e = n).prototype = Object.create(t.prototype), (e.prototype.constructor = e).__proto__ = t, n.prototype.fetch_ = function() {
+                        var i = this;
+                        return function n(r) {
+                            return void 0 === r && (r = 0), fetch(i.base_ + "?per_page=30&page=" + r).then(function(e) {
+                                return e.json()
+                            }).then(function(e) {
+                                if (!(e instanceof Array)) throw new TypeError;
+                                if (i.name_) {
+                                    var t = e.find(function(e) {
+                                        return e.name === i.name_
+                                    });
+                                    return t || 30 !== e.length ? t ? [i.format_(t.stargazers_count) + " Stars", i.format_(t.forks_count) + " Forks"] : [] : n(r + 1)
+                                }
+                                return [e.length + " Repositories"]
+                            })
+                        }()
+                    }, n
+                }(function() {
+                    function e(e) {
+                        var t = "string" == typeof e ? document.querySelector(e) : e;
+                        if (!(t instanceof HTMLAnchorElement)) throw new ReferenceError;
+                        this.el_ = t, this.base_ = this.el_.href, this.salt_ = this.hash_(this.base_)
+                    }
+                    var t = e.prototype;
+                    return t.fetch = function() {
+                        var n = this;
+                        return new Promise(function(t) {
+                            var e = l.a.getJSON(n.salt_ + ".cache-source");
+                            void 0 !== e ? t(e) : n.fetch_().then(function(e) {
+                                l.a.set(n.salt_ + ".cache-source", e, {
+                                    expires: 1 / 96
+                                }), t(e)
+                            })
+                        })
+                    }, t.fetch_ = function() {
+                        throw new Error("fetch_(): Not implemented")
+                    }, t.format_ = function(e) {
+                        return 1e4 < e ? (e / 1e3).toFixed(0) + "k" : 1e3 < e ? (e / 1e3).toFixed(1) + "k" : "" + e
+                    }, t.hash_ = function(e) {
+                        var t = 0;
+                        if (0 === e.length) return t;
+                        for (var n = 0, r = e.length; n < r; n++) t = (t << 5) - t + e.charCodeAt(n), t |= 0;
+                        return t
+                    }, e
+                }())
+            },
+            Repository: n(10).a
+        },
+        f = {
+            Toggle: function() {
+                function e(e) {
+                    var t = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(t instanceof Node)) throw new ReferenceError;
+                    this.el_ = t;
+                    var n = document.querySelector("[data-md-component=header]");
+                    this.height_ = n.offsetHeight, this.active_ = !1
+                }
+                var t = e.prototype;
+                return t.update = function() {
+                    var e = window.pageYOffset >= this.el_.children[0].offsetTop + (5 - this.height_);
+                    e !== this.active_ && (this.el_.dataset.mdState = (this.active_ = e) ? "hidden" : "")
+                }, t.reset = function() {
+                    this.el_.dataset.mdState = "", this.active_ = !1
+                }, e
+            }()
+        };
+    t.a = {
+        Event: r,
+        Header: i,
+        Nav: o,
+        Search: a,
+        Sidebar: s,
+        Source: u,
+        Tabs: f
+    }
+}, function(t, e, n) {
+    (function(e) {
+        t.exports = e.lunr = n(24)
+    }).call(this, n(4))
+}, function(e, f, d) {
+    "use strict";
+    (function(t) {
+        var e = d(8),
+            n = setTimeout;
+
+        function r() {}
+
+        function o(e) {
+            if (!(this instanceof o)) throw new TypeError("Promises must be constructed via new");
+            if ("function" != typeof e) throw new TypeError("not a function");
+            this._state = 0, this._handled = !1, this._value = void 0, this._deferreds = [], u(e, this)
+        }
+
+        function i(n, r) {
+            for (; 3 === n._state;) n = n._value;
+            0 !== n._state ? (n._handled = !0, o._immediateFn(function() {
+                var e = 1 === n._state ? r.onFulfilled : r.onRejected;
+                if (null !== e) {
+                    var t;
+                    try {
+                        t = e(n._value)
+                    } catch (e) {
+                        return void s(r.promise, e)
+                    }
+                    a(r.promise, t)
+                } else(1 === n._state ? a : s)(r.promise, n._value)
+            })) : n._deferreds.push(r)
+        }
+
+        function a(t, e) {
+            try {
+                if (e === t) throw new TypeError("A promise cannot be resolved with itself.");
+                if (e && ("object" == typeof e || "function" == typeof e)) {
+                    var n = e.then;
+                    if (e instanceof o) return t._state = 3, t._value = e, void c(t);
+                    if ("function" == typeof n) return void u((r = n, i = e, function() {
+                        r.apply(i, arguments)
+                    }), t)
+                }
+                t._state = 1, t._value = e, c(t)
+            } catch (e) {
+                s(t, e)
+            }
+            var r, i
+        }
+
+        function s(e, t) {
+            e._state = 2, e._value = t, c(e)
+        }
+
+        function c(e) {
+            2 === e._state && 0 === e._deferreds.length && o._immediateFn(function() {
+                e._handled || o._unhandledRejectionFn(e._value)
+            });
+            for (var t = 0, n = e._deferreds.length; t < n; t++) i(e, e._deferreds[t]);
+            e._deferreds = null
+        }
+
+        function l(e, t, n) {
+            this.onFulfilled = "function" == typeof e ? e : null, this.onRejected = "function" == typeof t ? t : null, this.promise = n
+        }
+
+        function u(e, t) {
+            var n = !1;
+            try {
+                e(function(e) {
+                    n || (n = !0, a(t, e))
+                }, function(e) {
+                    n || (n = !0, s(t, e))
+                })
+            } catch (e) {
+                if (n) return;
+                n = !0, s(t, e)
+            }
+        }
+        o.prototype.catch = function(e) {
+            return this.then(null, e)
+        }, o.prototype.then = function(e, t) {
+            var n = new this.constructor(r);
+            return i(this, new l(e, t, n)), n
+        }, o.prototype.finally = e.a, o.all = function(t) {
+            return new o(function(r, i) {
+                if (!t || void 0 === t.length) throw new TypeError("Promise.all accepts an array");
+                var o = Array.prototype.slice.call(t);
+                if (0 === o.length) return r([]);
+                var a = o.length;
+
+                function s(t, e) {
+                    try {
+                        if (e && ("object" == typeof e || "function" == typeof e)) {
+                            var n = e.then;
+                            if ("function" == typeof n) return void n.call(e, function(e) {
+                                s(t, e)
+                            }, i)
+                        }
+                        o[t] = e, 0 == --a && r(o)
+                    } catch (e) {
+                        i(e)
+                    }
+                }
+                for (var e = 0; e < o.length; e++) s(e, o[e])
+            })
+        }, o.resolve = function(t) {
+            return t && "object" == typeof t && t.constructor === o ? t : new o(function(e) {
+                e(t)
+            })
+        }, o.reject = function(n) {
+            return new o(function(e, t) {
+                t(n)
+            })
+        }, o.race = function(i) {
+            return new o(function(e, t) {
+                for (var n = 0, r = i.length; n < r; n++) i[n].then(e, t)
+            })
+        }, o._immediateFn = "function" == typeof t && function(e) {
+            t(e)
+        } || function(e) {
+            n(e, 0)
+        }, o._unhandledRejectionFn = function(e) {
+            "undefined" != typeof console && console && console.warn("Possible Unhandled Promise Rejection:", e)
+        }, f.a = o
+    }).call(this, d(21).setImmediate)
+}, function(e, t, n) {
+    "use strict";
+
+    function r(e, t) {
+        var n = document.createElement(e);
+        t && Array.prototype.forEach.call(Object.keys(t), function(e) {
+            n.setAttribute(e, t[e])
+        });
+        for (var r = arguments.length, i = new Array(2 < r ? r - 2 : 0), o = 2; o < r; o++) i[o - 2] = arguments[o];
+        return function t(e) {
+            Array.prototype.forEach.call(e, function(e) {
+                "string" == typeof e || "number" == typeof e ? n.textContent += e : Array.isArray(e) ? t(e) : void 0 !== e.__html ? n.innerHTML += e.__html : e instanceof Node && n.appendChild(e)
+            })
+        }(i), n
+    }
+    n.r(t), n.d(t, "createElement", function() {
+        return r
+    })
+}, function(e, t) {
+    var n;
+    n = function() {
+        return this
+    }();
+    try {
+        n = n || new Function("return this")()
+    } catch (e) {
+        "object" == typeof window && (n = window)
+    }
+    e.exports = n
+}, function(e, t, n) {
+    var r;
+    r = function() {
+        return function(n) {
+            var r = {};
+
+            function i(e) {
+                if (r[e]) return r[e].exports;
+                var t = r[e] = {
+                    i: e,
+                    l: !1,
+                    exports: {}
+                };
+                return n[e].call(t.exports, t, t.exports, i), t.l = !0, t.exports
+            }
+            return i.m = n, i.c = r, i.d = function(e, t, n) {
+                i.o(e, t) || Object.defineProperty(e, t, {
+                    enumerable: !0,
+                    get: n
+                })
+            }, i.r = function(e) {
+                "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
+                    value: "Module"
+                }), Object.defineProperty(e, "__esModule", {
+                    value: !0
+                })
+            }, i.t = function(t, e) {
+                if (1 & e && (t = i(t)), 8 & e) return t;
+                if (4 & e && "object" == typeof t && t && t.__esModule) return t;
+                var n = Object.create(null);
+                if (i.r(n), Object.defineProperty(n, "default", {
+                        enumerable: !0,
+                        value: t
+                    }), 2 & e && "string" != typeof t)
+                    for (var r in t) i.d(n, r, function(e) {
+                        return t[e]
+                    }.bind(null, r));
+                return n
+            }, i.n = function(e) {
+                var t = e && e.__esModule ? function() {
+                    return e.default
+                } : function() {
+                    return e
+                };
+                return i.d(t, "a", t), t
+            }, i.o = function(e, t) {
+                return Object.prototype.hasOwnProperty.call(e, t)
+            }, i.p = "", i(i.s = 0)
+        }([function(e, t, n) {
+            "use strict";
+            var i = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(e) {
+                    return typeof e
+                } : function(e) {
+                    return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e
+                },
+                o = function() {
+                    function r(e, t) {
+                        for (var n = 0; n < t.length; n++) {
+                            var r = t[n];
+                            r.enumerable = r.enumerable || !1, r.configurable = !0, "value" in r && (r.writable = !0), Object.defineProperty(e, r.key, r)
+                        }
+                    }
+                    return function(e, t, n) {
+                        return t && r(e.prototype, t), n && r(e, n), e
+                    }
+                }(),
+                a = r(n(1)),
+                s = r(n(3)),
+                c = r(n(4));
+
+            function r(e) {
+                return e && e.__esModule ? e : {
+                    default: e
+                }
+            }
+            var l = function(e) {
+                function r(e, t) {
+                    ! function(e, t) {
+                        if (!(e instanceof t)) throw new TypeError("Cannot call a class as a function")
+                    }(this, r);
+                    var n = function(e, t) {
+                        if (!e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+                        return !t || "object" != typeof t && "function" != typeof t ? e : t
+                    }(this, (r.__proto__ || Object.getPrototypeOf(r)).call(this));
+                    return n.resolveOptions(t), n.listenClick(e), n
+                }
+                return function(e, t) {
+                    if ("function" != typeof t && null !== t) throw new TypeError("Super expression must either be null or a function, not " + typeof t);
+                    e.prototype = Object.create(t && t.prototype, {
+                        constructor: {
+                            value: e,
+                            enumerable: !1,
+                            writable: !0,
+                            configurable: !0
+                        }
+                    }), t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : e.__proto__ = t)
+                }(r, s.default), o(r, [{
+                    key: "resolveOptions",
+                    value: function() {
+                        var e = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {};
+                        this.action = "function" == typeof e.action ? e.action : this.defaultAction, this.target = "function" == typeof e.target ? e.target : this.defaultTarget, this.text = "function" == typeof e.text ? e.text : this.defaultText, this.container = "object" === i(e.container) ? e.container : document.body
+                    }
+                }, {
+                    key: "listenClick",
+                    value: function(e) {
+                        var t = this;
+                        this.listener = (0, c.default)(e, "click", function(e) {
+                            return t.onClick(e)
+                        })
+                    }
+                }, {
+                    key: "onClick",
+                    value: function(e) {
+                        var t = e.delegateTarget || e.currentTarget;
+                        this.clipboardAction && (this.clipboardAction = null), this.clipboardAction = new a.default({
+                            action: this.action(t),
+                            target: this.target(t),
+                            text: this.text(t),
+                            container: this.container,
+                            trigger: t,
+                            emitter: this
+                        })
+                    }
+                }, {
+                    key: "defaultAction",
+                    value: function(e) {
+                        return u("action", e)
+                    }
+                }, {
+                    key: "defaultTarget",
+                    value: function(e) {
+                        var t = u("target", e);
+                        if (t) return document.querySelector(t)
+                    }
+                }, {
+                    key: "defaultText",
+                    value: function(e) {
+                        return u("text", e)
+                    }
+                }, {
+                    key: "destroy",
+                    value: function() {
+                        this.listener.destroy(), this.clipboardAction && (this.clipboardAction.destroy(), this.clipboardAction = null)
+                    }
+                }], [{
+                    key: "isSupported",
+                    value: function() {
+                        var e = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : ["copy", "cut"],
+                            t = "string" == typeof e ? [e] : e,
+                            n = !!document.queryCommandSupported;
+                        return t.forEach(function(e) {
+                            n = n && !!document.queryCommandSupported(e)
+                        }), n
+                    }
+                }]), r
+            }();
+
+            function u(e, t) {
+                var n = "data-clipboard-" + e;
+                if (t.hasAttribute(n)) return t.getAttribute(n)
+            }
+            e.exports = l
+        }, function(e, t, n) {
+            "use strict";
+            var r, i = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(e) {
+                    return typeof e
+                } : function(e) {
+                    return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e
+                },
+                o = function() {
+                    function r(e, t) {
+                        for (var n = 0; n < t.length; n++) {
+                            var r = t[n];
+                            r.enumerable = r.enumerable || !1, r.configurable = !0, "value" in r && (r.writable = !0), Object.defineProperty(e, r.key, r)
+                        }
+                    }
+                    return function(e, t, n) {
+                        return t && r(e.prototype, t), n && r(e, n), e
+                    }
+                }(),
+                a = n(2),
+                s = (r = a) && r.__esModule ? r : {
+                    default: r
+                };
+            var c = function() {
+                function t(e) {
+                    ! function(e, t) {
+                        if (!(e instanceof t)) throw new TypeError("Cannot call a class as a function")
+                    }(this, t), this.resolveOptions(e), this.initSelection()
+                }
+                return o(t, [{
+                    key: "resolveOptions",
+                    value: function() {
+                        var e = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {};
+                        this.action = e.action, this.container = e.container, this.emitter = e.emitter, this.target = e.target, this.text = e.text, this.trigger = e.trigger, this.selectedText = ""
+                    }
+                }, {
+                    key: "initSelection",
+                    value: function() {
+                        this.text ? this.selectFake() : this.target && this.selectTarget()
+                    }
+                }, {
+                    key: "selectFake",
+                    value: function() {
+                        var e = this,
+                            t = "rtl" == document.documentElement.getAttribute("dir");
+                        this.removeFake(), this.fakeHandlerCallback = function() {
+                            return e.removeFake()
+                        }, this.fakeHandler = this.container.addEventListener("click", this.fakeHandlerCallback) || !0, this.fakeElem = document.createElement("textarea"), this.fakeElem.style.fontSize = "12pt", this.fakeElem.style.border = "0", this.fakeElem.style.padding = "0", this.fakeElem.style.margin = "0", this.fakeElem.style.position = "absolute", this.fakeElem.style[t ? "right" : "left"] = "-9999px";
+                        var n = window.pageYOffset || document.documentElement.scrollTop;
+                        this.fakeElem.style.top = n + "px", this.fakeElem.setAttribute("readonly", ""), this.fakeElem.value = this.text, this.container.appendChild(this.fakeElem), this.selectedText = (0, s.default)(this.fakeElem), this.copyText()
+                    }
+                }, {
+                    key: "removeFake",
+                    value: function() {
+                        this.fakeHandler && (this.container.removeEventListener("click", this.fakeHandlerCallback), this.fakeHandler = null, this.fakeHandlerCallback = null), this.fakeElem && (this.container.removeChild(this.fakeElem), this.fakeElem = null)
+                    }
+                }, {
+                    key: "selectTarget",
+                    value: function() {
+                        this.selectedText = (0, s.default)(this.target), this.copyText()
+                    }
+                }, {
+                    key: "copyText",
+                    value: function() {
+                        var t = void 0;
+                        try {
+                            t = document.execCommand(this.action)
+                        } catch (e) {
+                            t = !1
+                        }
+                        this.handleResult(t)
+                    }
+                }, {
+                    key: "handleResult",
+                    value: function(e) {
+                        this.emitter.emit(e ? "success" : "error", {
+                            action: this.action,
+                            text: this.selectedText,
+                            trigger: this.trigger,
+                            clearSelection: this.clearSelection.bind(this)
+                        })
+                    }
+                }, {
+                    key: "clearSelection",
+                    value: function() {
+                        this.trigger && this.trigger.focus(), window.getSelection().removeAllRanges()
+                    }
+                }, {
+                    key: "destroy",
+                    value: function() {
+                        this.removeFake()
+                    }
+                }, {
+                    key: "action",
+                    set: function() {
+                        var e = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : "copy";
+                        if (this._action = e, "copy" !== this._action && "cut" !== this._action) throw new Error('Invalid "action" value, use either "copy" or "cut"')
+                    },
+                    get: function() {
+                        return this._action
+                    }
+                }, {
+                    key: "target",
+                    set: function(e) {
+                        if (void 0 !== e) {
+                            if (!e || "object" !== (void 0 === e ? "undefined" : i(e)) || 1 !== e.nodeType) throw new Error('Invalid "target" value, use a valid Element');
+                            if ("copy" === this.action && e.hasAttribute("disabled")) throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
+                            if ("cut" === this.action && (e.hasAttribute("readonly") || e.hasAttribute("disabled"))) throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
+                            this._target = e
+                        }
+                    },
+                    get: function() {
+                        return this._target
+                    }
+                }]), t
+            }();
+            e.exports = c
+        }, function(e, t) {
+            e.exports = function(e) {
+                var t;
+                if ("SELECT" === e.nodeName) e.focus(), t = e.value;
+                else if ("INPUT" === e.nodeName || "TEXTAREA" === e.nodeName) {
+                    var n = e.hasAttribute("readonly");
+                    n || e.setAttribute("readonly", ""), e.select(), e.setSelectionRange(0, e.value.length), n || e.removeAttribute("readonly"), t = e.value
+                } else {
+                    e.hasAttribute("contenteditable") && e.focus();
+                    var r = window.getSelection(),
+                        i = document.createRange();
+                    i.selectNodeContents(e), r.removeAllRanges(), r.addRange(i), t = r.toString()
+                }
+                return t
+            }
+        }, function(e, t) {
+            function n() {}
+            n.prototype = {
+                on: function(e, t, n) {
+                    var r = this.e || (this.e = {});
+                    return (r[e] || (r[e] = [])).push({
+                        fn: t,
+                        ctx: n
+                    }), this
+                },
+                once: function(e, t, n) {
+                    var r = this;
+
+                    function i() {
+                        r.off(e, i), t.apply(n, arguments)
+                    }
+                    return i._ = t, this.on(e, i, n)
+                },
+                emit: function(e) {
+                    for (var t = [].slice.call(arguments, 1), n = ((this.e || (this.e = {}))[e] || []).slice(), r = 0, i = n.length; r < i; r++) n[r].fn.apply(n[r].ctx, t);
+                    return this
+                },
+                off: function(e, t) {
+                    var n = this.e || (this.e = {}),
+                        r = n[e],
+                        i = [];
+                    if (r && t)
+                        for (var o = 0, a = r.length; o < a; o++) r[o].fn !== t && r[o].fn._ !== t && i.push(r[o]);
+                    return i.length ? n[e] = i : delete n[e], this
+                }
+            }, e.exports = n
+        }, function(e, t, n) {
+            var d = n(5),
+                h = n(6);
+            e.exports = function(e, t, n) {
+                if (!e && !t && !n) throw new Error("Missing required arguments");
+                if (!d.string(t)) throw new TypeError("Second argument must be a String");
+                if (!d.fn(n)) throw new TypeError("Third argument must be a Function");
+                if (d.node(e)) return u = t, f = n, (l = e).addEventListener(u, f), {
+                    destroy: function() {
+                        l.removeEventListener(u, f)
+                    }
+                };
+                if (d.nodeList(e)) return a = e, s = t, c = n, Array.prototype.forEach.call(a, function(e) {
+                    e.addEventListener(s, c)
+                }), {
+                    destroy: function() {
+                        Array.prototype.forEach.call(a, function(e) {
+                            e.removeEventListener(s, c)
+                        })
+                    }
+                };
+                if (d.string(e)) return r = e, i = t, o = n, h(document.body, r, i, o);
+                throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");
+                var r, i, o, a, s, c, l, u, f
+            }
+        }, function(e, n) {
+            n.node = function(e) {
+                return void 0 !== e && e instanceof HTMLElement && 1 === e.nodeType
+            }, n.nodeList = function(e) {
+                var t = Object.prototype.toString.call(e);
+                return void 0 !== e && ("[object NodeList]" === t || "[object HTMLCollection]" === t) && "length" in e && (0 === e.length || n.node(e[0]))
+            }, n.string = function(e) {
+                return "string" == typeof e || e instanceof String
+            }, n.fn = function(e) {
+                return "[object Function]" === Object.prototype.toString.call(e)
+            }
+        }, function(e, t, n) {
+            var a = n(7);
+
+            function o(e, t, n, r, i) {
+                var o = function(t, n, e, r) {
+                    return function(e) {
+                        e.delegateTarget = a(e.target, n), e.delegateTarget && r.call(t, e)
+                    }
+                }.apply(this, arguments);
+                return e.addEventListener(n, o, i), {
+                    destroy: function() {
+                        e.removeEventListener(n, o, i)
+                    }
+                }
+            }
+            e.exports = function(e, t, n, r, i) {
+                return "function" == typeof e.addEventListener ? o.apply(null, arguments) : "function" == typeof n ? o.bind(null, document).apply(null, arguments) : ("string" == typeof e && (e = document.querySelectorAll(e)), Array.prototype.map.call(e, function(e) {
+                    return o(e, t, n, r, i)
+                }))
+            }
+        }, function(e, t) {
+            if ("undefined" != typeof Element && !Element.prototype.matches) {
+                var n = Element.prototype;
+                n.matches = n.matchesSelector || n.mozMatchesSelector || n.msMatchesSelector || n.oMatchesSelector || n.webkitMatchesSelector
+            }
+            e.exports = function(e, t) {
+                for (; e && 9 !== e.nodeType;) {
+                    if ("function" == typeof e.matches && e.matches(t)) return e;
+                    e = e.parentNode
+                }
+            }
+        }])
+    }, e.exports = r()
+}, function(r, i, o) {
+    var a, s;
+    ! function(e) {
+        if (void 0 === (s = "function" == typeof(a = e) ? a.call(i, o, i, r) : a) || (r.exports = s), !0, r.exports = e(), !!0) {
+            var t = window.Cookies,
+                n = window.Cookies = e();
+            n.noConflict = function() {
+                return window.Cookies = t, n
+            }
+        }
+    }(function() {
+        function m() {
+            for (var e = 0, t = {}; e < arguments.length; e++) {
+                var n = arguments[e];
+                for (var r in n) t[r] = n[r]
+            }
+            return t
+        }
+        return function e(h) {
+            function p(e, t, n) {
+                var r;
+                if ("undefined" != typeof document) {
+                    if (1 < arguments.length) {
+                        if ("number" == typeof(n = m({
+                                path: "/"
+                            }, p.defaults, n)).expires) {
+                            var i = new Date;
+                            i.setMilliseconds(i.getMilliseconds() + 864e5 * n.expires), n.expires = i
+                        }
+                        n.expires = n.expires ? n.expires.toUTCString() : "";
+                        try {
+                            r = JSON.stringify(t), /^[\{\[]/.test(r) && (t = r)
+                        } catch (e) {}
+                        t = h.write ? h.write(t, e) : encodeURIComponent(String(t)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent), e = (e = (e = encodeURIComponent(String(e))).replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent)).replace(/[\(\)]/g, escape);
+                        var o = "";
+                        for (var a in n) n[a] && (o += "; " + a, !0 !== n[a] && (o += "=" + n[a]));
+                        return document.cookie = e + "=" + t + o
+                    }
+                    e || (r = {});
+                    for (var s = document.cookie ? document.cookie.split("; ") : [], c = /(%[0-9A-Z]{2})+/g, l = 0; l < s.length; l++) {
+                        var u = s[l].split("="),
+                            f = u.slice(1).join("=");
+                        this.json || '"' !== f.charAt(0) || (f = f.slice(1, -1));
+                        try {
+                            var d = u[0].replace(c, decodeURIComponent);
+                            if (f = h.read ? h.read(f, d) : h(f, d) || f.replace(c, decodeURIComponent), this.json) try {
+                                f = JSON.parse(f)
+                            } catch (e) {}
+                            if (e === d) {
+                                r = f;
+                                break
+                            }
+                            e || (r[d] = f)
+                        } catch (e) {}
+                    }
+                    return r
+                }
+            }
+            return (p.set = p).get = function(e) {
+                return p.call(p, e)
+            }, p.getJSON = function() {
+                return p.apply({
+                    json: !0
+                }, [].slice.call(arguments))
+            }, p.defaults = {}, p.remove = function(e, t) {
+                p(e, "", m(t, {
+                    expires: -1
+                }))
+            }, p.withConverter = e, p
+        }(function() {})
+    })
+}, function(e, t, n) {
+    "use strict";
+    n.r(t);
+    var r = "function" == typeof fetch ? fetch.bind() : function(i, o) {
+        return o = o || {}, new Promise(function(e, t) {
+            var n = new XMLHttpRequest;
+            for (var r in n.open(o.method || "get", i, !0), o.headers) n.setRequestHeader(r, o.headers[r]);
+
+            function s() {
+                var r, i = [],
+                    o = [],
+                    a = {};
+                return n.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, function(e, t, n) {
+                    i.push(t = t.toLowerCase()), o.push([t, n]), r = a[t], a[t] = r ? r + "," + n : n
+                }), {
+                    ok: 2 == (n.status / 100 | 0),
+                    status: n.status,
+                    statusText: n.statusText,
+                    url: n.responseURL,
+                    clone: s,
+                    text: function() {
+                        return Promise.resolve(n.responseText)
+                    },
+                    json: function() {
+                        return Promise.resolve(n.responseText).then(JSON.parse)
+                    },
+                    blob: function() {
+                        return Promise.resolve(new Blob([n.response]))
+                    },
+                    headers: {
+                        keys: function() {
+                            return i
+                        },
+                        entries: function() {
+                            return o
+                        },
+                        get: function(e) {
+                            return a[e.toLowerCase()]
+                        },
+                        has: function(e) {
+                            return e.toLowerCase() in a
+                        }
+                    }
+                }
+            }
+            n.withCredentials = "include" == o.credentials, n.onload = function() {
+                e(s())
+            }, n.onerror = t, n.send(o.body || null)
+        })
+    };
+    t.default = r
+}, function(e, t, n) {
+    "use strict";
+    t.a = function(t) {
+        var n = this.constructor;
+        return this.then(function(e) {
+            return n.resolve(t()).then(function() {
+                return e
+            })
+        }, function(e) {
+            return n.resolve(t()).then(function() {
+                return n.reject(e)
+            })
+        })
+    }
+}, function(e, n, r) {
+    "use strict";
+    (function(f) {
+        r.d(n, "a", function() {
+            return t
+        });
+        var e = r(1),
+            d = r.n(e),
+            h = function(e) {
+                var t = document.getElementsByName("lang:" + e)[0];
+                if (!(t instanceof HTMLMetaElement)) throw new ReferenceError;
+                return t.content
+            },
+            t = function() {
+                function e(e, t) {
+                    var n = "string" == typeof e ? document.querySelector(e) : e;
+                    if (!(n instanceof HTMLElement)) throw new ReferenceError;
+                    this.el_ = n;
+                    var r = Array.prototype.slice.call(this.el_.children),
+                        i = r[0],
+                        o = r[1];
+                    this.data_ = t, this.meta_ = i, this.list_ = o, this.message_ = {
+                        placeholder: this.meta_.textContent,
+                        none: h("search.result.none"),
+                        one: h("search.result.one"),
+                        other: h("search.result.other")
+                    };
+                    var a = h("search.tokenizer");
+                    a.length && (d.a.tokenizer.separator = a), this.lang_ = h("search.language").split(",").filter(Boolean).map(function(e) {
+                        return e.trim()
+                    })
+                }
+                return e.prototype.update = function(e) {
+                    var t, a = this;
+                    if ("focus" !== e.type || this.index_) {
+                        if ("focus" === e.type || "keyup" === e.type) {
+                            var n = e.target;
+                            if (!(n instanceof HTMLInputElement)) throw new ReferenceError;
+                            if (!this.index_ || n.value === this.value_) return;
+                            for (; this.list_.firstChild;) this.list_.removeChild(this.list_.firstChild);
+                            if (this.value_ = n.value, 0 === this.value_.length) return void(this.meta_.textContent = this.message_.placeholder);
+                            var r = this.index_.query(function(t) {
+                                    a.value_.toLowerCase().split(" ").filter(Boolean).forEach(function(e) {
+                                        t.term(e, {
+                                            wildcard: d.a.Query.wildcard.TRAILING
+                                        })
+                                    })
+                                }).reduce(function(e, t) {
+                                    var n = a.docs_.get(t.ref);
+                                    if (n.parent) {
+                                        var r = n.parent.location;
+                                        e.set(r, (e.get(r) || []).concat(t))
+                                    } else {
+                                        var i = n.location;
+                                        e.set(i, e.get(i) || [])
+                                    }
+                                    return e
+                                }, new Map),
+                                i = (t = this.value_.trim(), t.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&")).replace(new RegExp(d.a.tokenizer.separator, "img"), "|"),
+                                s = new RegExp("(^|" + d.a.tokenizer.separator + ")(" + i + ")", "img"),
+                                c = function(e, t, n) {
+                                    return t + "<em>" + n + "</em>"
+                                };
+                            this.stack_ = [], r.forEach(function(e, t) {
+                                var n, r = a.docs_.get(t),
+                                    i = f.createElement("li", {
+                                        class: "md-search-result__item"
+                                    }, f.createElement("a", {
+                                        href: r.location,
+                                        title: r.title,
+                                        class: "md-search-result__link",
+                                        tabindex: "-1"
+                                    }, f.createElement("article", {
+                                        class: "md-search-result__article md-search-result__article--document"
+                                    }, f.createElement("h1", {
+                                        class: "md-search-result__title"
+                                    }, {
+                                        __html: r.title.replace(s, c)
+                                    }), r.text.length ? f.createElement("p", {
+                                        class: "md-search-result__teaser"
+                                    }, {
+                                        __html: r.text.replace(s, c)
+                                    }) : {}))),
+                                    o = e.map(function(t) {
+                                        return function() {
+                                            var e = a.docs_.get(t.ref);
+                                            i.appendChild(f.createElement("a", {
+                                                href: e.location,
+                                                title: e.title,
+                                                class: "md-search-result__link",
+                                                "data-md-rel": "anchor",
+                                                tabindex: "-1"
+                                            }, f.createElement("article", {
+                                                class: "md-search-result__article"
+                                            }, f.createElement("h1", {
+                                                class: "md-search-result__title"
+                                            }, {
+                                                __html: e.title.replace(s, c)
+                                            }), e.text.length ? f.createElement("p", {
+                                                class: "md-search-result__teaser"
+                                            }, {
+                                                __html: function(e, t) {
+                                                    var n = t;
+                                                    if (e.length > n) {
+                                                        for (;
+                                                            " " !== e[n] && 0 < --n;);
+                                                        return e.substring(0, n) + "..."
+                                                    }
+                                                    return e
+                                                }(e.text.replace(s, c), 400)
+                                            }) : {})))
+                                        }
+                                    });
+                                (n = a.stack_).push.apply(n, [function() {
+                                    return a.list_.appendChild(i)
+                                }].concat(o))
+                            });
+                            var o = this.el_.parentNode;
+                            if (!(o instanceof HTMLElement)) throw new ReferenceError;
+                            for (; this.stack_.length && o.offsetHeight >= o.scrollHeight - 16;) this.stack_.shift()();
+                            var l = this.list_.querySelectorAll("[data-md-rel=anchor]");
+                            switch (Array.prototype.forEach.call(l, function(r) {
+                                ["click", "keydown"].forEach(function(n) {
+                                    r.addEventListener(n, function(e) {
+                                        if ("keydown" !== n || 13 === e.keyCode) {
+                                            var t = document.querySelector("[data-md-toggle=search]");
+                                            if (!(t instanceof HTMLInputElement)) throw new ReferenceError;
+                                            t.checked && (t.checked = !1, t.dispatchEvent(new CustomEvent("change"))), e.preventDefault(), setTimeout(function() {
+                                                document.location.href = r.href
+                                            }, 100)
+                                        }
+                                    })
+                                })
+                            }), r.size) {
+                                case 0:
+                                    this.meta_.textContent = this.message_.none;
+                                    break;
+                                case 1:
+                                    this.meta_.textContent = this.message_.one;
+                                    break;
+                                default:
+                                    this.meta_.textContent = this.message_.other.replace("#", r.size)
+                            }
+                        }
+                    } else {
+                        var u = function(e) {
+                            a.docs_ = e.reduce(function(e, t) {
+                                var n, r, i, o = t.location.split("#"),
+                                    a = o[0],
+                                    s = o[1];
+                                return t.text = (n = t.text, r = document.createTextNode(n), (i = document.createElement("p")).appendChild(r), i.innerHTML), s && (t.parent = e.get(a), t.parent && !t.parent.done && (t.parent.title = t.title, t.parent.text = t.text, t.parent.done = !0)), t.text = t.text.replace(/\n/g, " ").replace(/\s+/g, " ").replace(/\s+([,.:;!?])/g, function(e, t) {
+                                    return t
+                                }), t.parent && t.parent.title === t.title || e.set(t.location, t), e
+                            }, new Map);
+                            var i = a.docs_,
+                                o = a.lang_;
+                            a.stack_ = [], a.index_ = d()(function() {
+                                var e, t = this,
+                                    n = {
+                                        "search.pipeline.trimmer": d.a.trimmer,
+                                        "search.pipeline.stopwords": d.a.stopWordFilter
+                                    },
+                                    r = Object.keys(n).reduce(function(e, t) {
+                                        return h(t).match(/^false$/i) || e.push(n[t]), e
+                                    }, []);
+                                this.pipeline.reset(), r && (e = this.pipeline).add.apply(e, r), 1 === o.length && "en" !== o[0] && d.a[o[0]] ? this.use(d.a[o[0]]) : 1 < o.length && this.use(d.a.multiLanguage.apply(d.a, o)), this.field("title", {
+                                    boost: 10
+                                }), this.field("text"), this.ref("location"), i.forEach(function(e) {
+                                    return t.add(e)
+                                })
+                            });
+                            var t = a.el_.parentNode;
+                            if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                            t.addEventListener("scroll", function() {
+                                for (; a.stack_.length && t.scrollTop + t.offsetHeight >= t.scrollHeight - 16;) a.stack_.splice(0, 10).forEach(function(e) {
+                                    return e()
+                                })
+                            })
+                        };
+                        setTimeout(function() {
+                            return "function" == typeof a.data_ ? a.data_().then(u) : u(a.data_)
+                        }, 250)
+                    }
+                }, e
+            }()
+    }).call(this, r(3))
+}, function(e, n, r) {
+    "use strict";
+    (function(t) {
+        r.d(n, "a", function() {
+            return e
+        });
+        var e = function() {
+            function e(e) {
+                var t = "string" == typeof e ? document.querySelector(e) : e;
+                if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                this.el_ = t
+            }
+            return e.prototype.initialize = function(e) {
+                e.length && this.el_.children.length && this.el_.children[this.el_.children.length - 1].appendChild(t.createElement("ul", {
+                    class: "md-source__facts"
+                }, e.map(function(e) {
+                    return t.createElement("li", {
+                        class: "md-source__fact"
+                    }, e)
+                }))), this.el_.dataset.mdState = "done"
+            }, e
+        }()
+    }).call(this, r(3))
+}, , , function(e, n, c) {
+    "use strict";
+    c.r(n),
+        function(o) {
+            c.d(n, "app", function() {
+                return t
+            });
+            c(14), c(15), c(16), c(17), c(18), c(19), c(20);
+            var r = c(2),
+                e = c(5),
+                a = c.n(e),
+                i = c(0);
+            window.Promise = window.Promise || r.a;
+            var s = function(e) {
+                var t = document.getElementsByName("lang:" + e)[0];
+                if (!(t instanceof HTMLMetaElement)) throw new ReferenceError;
+                return t.content
+            };
+            var t = {
+                initialize: function(t) {
+                    new i.a.Event.Listener(document, "DOMContentLoaded", function() {
+                        if (!(document.body instanceof HTMLElement)) throw new ReferenceError;
+                        Modernizr.addTest("ios", function() {
+                            return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
+                        });
+                        var e = document.querySelectorAll("table:not([class])");
+                        if (Array.prototype.forEach.call(e, function(e) {
+                                var t = o.createElement("div", {
+                                    class: "md-typeset__scrollwrap"
+                                }, o.createElement("div", {
+                                    class: "md-typeset__table"
+                                }));
+                                e.nextSibling ? e.parentNode.insertBefore(t, e.nextSibling) : e.parentNode.appendChild(t), t.children[0].appendChild(e)
+                            }), a.a.isSupported()) {
+                            var t = document.querySelectorAll(".codehilite > pre, pre > code");
+                            Array.prototype.forEach.call(t, function(e, t) {
+                                var n = "__code_" + t,
+                                    r = o.createElement("button", {
+                                        class: "md-clipboard",
+                                        title: s("clipboard.copy"),
+                                        "data-clipboard-target": "#" + n + " pre, #" + n + " code"
+                                    }, o.createElement("span", {
+                                        class: "md-clipboard__message"
+                                    })),
+                                    i = e.parentNode;
+                                i.id = n, i.insertBefore(r, e)
+                            }), new a.a(".md-clipboard").on("success", function(e) {
+                                var t = e.trigger.querySelector(".md-clipboard__message");
+                                if (!(t instanceof HTMLElement)) throw new ReferenceError;
+                                e.clearSelection(), t.dataset.mdTimer && clearTimeout(parseInt(t.dataset.mdTimer, 10)), t.classList.add("md-clipboard__message--active"), t.innerHTML = s("clipboard.copied"), t.dataset.mdTimer = setTimeout(function() {
+                                    t.classList.remove("md-clipboard__message--active"), t.dataset.mdTimer = ""
+                                }, 2e3).toString()
+                            })
+                        }
+                        if (!Modernizr.details) {
+                            var n = document.querySelectorAll("details > summary");
+                            Array.prototype.forEach.call(n, function(e) {
+                                e.addEventListener("click", function(e) {
+                                    var t = e.target.parentNode;
+                                    t.hasAttribute("open") ? t.removeAttribute("open") : t.setAttribute("open", "")
+                                })
+                            })
+                        }
+                        var r = function() {
+                            if (document.location.hash) {
+                                var e = document.getElementById(document.location.hash.substring(1));
+                                if (!e) return;
+                                for (var t = e.parentNode; t && !(t instanceof HTMLDetailsElement);) t = t.parentNode;
+                                if (t && !t.open) {
+                                    t.open = !0;
+                                    var n = location.hash;
+                                    location.hash = " ", location.hash = n
+                                }
+                            }
+                        };
+                        if (window.addEventListener("hashchange", r), r(), Modernizr.ios) {
+                            var i = document.querySelectorAll("[data-md-scrollfix]");
+                            Array.prototype.forEach.call(i, function(t) {
+                                t.addEventListener("touchstart", function() {
+                                    var e = t.scrollTop;
+                                    0 === e ? t.scrollTop = 1 : e + t.offsetHeight === t.scrollHeight && (t.scrollTop = e - 1)
+                                })
+                            })
+                        }
+                    }).listen(), new i.a.Event.Listener(window, ["scroll", "resize", "orientationchange"], new i.a.Header.Shadow("[data-md-component=container]", "[data-md-component=header]")).listen(), new i.a.Event.Listener(window, ["scroll", "resize", "orientationchange"], new i.a.Header.Title("[data-md-component=title]", ".md-typeset h1")).listen(), document.querySelector("[data-md-component=hero]") && new i.a.Event.Listener(window, ["scroll", "resize", "orientationchange"], new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(), document.querySelector("[data-md-component=tabs]") && new i.a.Event.Listener(window, ["scroll", "resize", "orientationchange"], new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(), new i.a.Event.MatchMedia("(min-width: 1220px)", new i.a.Event.Listener(window, ["scroll", "resize", "orientationchange"], new i.a.Sidebar.Position("[data-md-component=navigation]", "[data-md-component=header]"))), document.querySelector("[data-md-component=toc]") && new i.a.Event.MatchMedia("(min-width: 960px)", new i.a.Event.Listener(window, ["scroll", "resize", "orientationchange"], new i.a.Sidebar.Position("[data-md-component=toc]", "[data-md-component=header]"))), new i.a.Event.MatchMedia("(min-width: 960px)", new i.a.Event.Listener(window, "scroll", new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));
+                    var e = document.querySelectorAll("[data-md-component=collapsible]");
+                    Array.prototype.forEach.call(e, function(e) {
+                            new i.a.Event.MatchMedia("(min-width: 1220px)", new i.a.Event.Listener(e.previousElementSibling, "click", new i.a.Nav.Collapse(e)))
+                        }), new i.a.Event.MatchMedia("(max-width: 1219px)", new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]", "change", new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))), document.querySelector("[data-md-component=search]") && (new i.a.Event.MatchMedia("(max-width: 959px)", new i.a.Event.Listener("[data-md-toggle=search]", "change", new i.a.Search.Lock("[data-md-toggle=search]")))),
+                        new i.a.Event.Listener(document.body, "keydown", function(e) {
+                            if (9 === e.keyCode) {
+                                var t = document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");
+                                Array.prototype.forEach.call(t, function(e) {
+                                    e.offsetHeight && (e.tabIndex = 0)
+                                })
+                            }
+                        }).listen(), new i.a.Event.Listener(document.body, "mousedown", function() {
+                            var e = document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");
+                            Array.prototype.forEach.call(e, function(e) {
+                                e.removeAttribute("tabIndex")
+                            })
+                        }).listen(), document.body.addEventListener("click", function() {
+                            "tabbing" === document.body.dataset.mdState && (document.body.dataset.mdState = "")
+                        }), new i.a.Event.MatchMedia("(max-width: 959px)", new i.a.Event.Listener("[data-md-component=navigation] [href^='#']", "click", function() {
+                            var e = document.querySelector("[data-md-toggle=drawer]");
+                            if (!(e instanceof HTMLInputElement)) throw new ReferenceError;
+                            e.checked && (e.checked = !1, e.dispatchEvent(new CustomEvent("change")))
+                        })),
+                        function() {
+                            var e = document.querySelector("[data-md-source]");
+                            if (!e) return r.a.resolve([]);
+                            if (!(e instanceof HTMLAnchorElement)) throw new ReferenceError;
+                            switch (e.dataset.mdSource) {
+                                case "github":
+                                    return new i.a.Source.Adapter.GitHub(e).fetch();
+                                default:
+                                    return r.a.resolve([])
+                            }
+                        }().then(function(t) {
+                            var e = document.querySelectorAll("[data-md-source]");
+                            Array.prototype.forEach.call(e, function(e) {
+                                new i.a.Source.Repository(e).initialize(t)
+                            })
+                        });
+                    var n = function() {
+                        var e = document.querySelectorAll("details");
+                        Array.prototype.forEach.call(e, function(e) {
+                            e.setAttribute("open", "")
+                        })
+                    };
+                    new i.a.Event.MatchMedia("print", {
+                        listen: n,
+                        unlisten: function() {}
+                    }), window.onbeforeprint = n
+                }
+            }
+        }.call(this, c(3))
+}, function(e, t, n) {
+    e.exports = n.p + "assets/images/icons/bitbucket.1b09e088.svg"
+}, function(e, t, n) {
+    e.exports = n.p + "assets/images/icons/github.f0b8504a.svg"
+}, function(e, t, n) {
+    e.exports = n.p + "assets/images/icons/gitlab.6dd19c00.svg"
+}, function(e, t) {
+    e.exports = "/Users/squidfunk/Desktop/General/Sources/mkdocs-material/material/application.4031d38b.css"
+}, function(e, t) {
+    e.exports = "/Users/squidfunk/Desktop/General/Sources/mkdocs-material/material/application-palette.224b79ff.css"
+}, function(e, t) {
+    ! function() {
+        if ("undefined" != typeof window) try {
+            var e = new window.CustomEvent("test", {
+                cancelable: !0
+            });
+            if (e.preventDefault(), !0 !== e.defaultPrevented) throw new Error("Could not prevent default")
+        } catch (e) {
+            var t = function(e, t) {
+                var n, r;
+                return (t = t || {}).bubbles = !!t.bubbles, t.cancelable = !!t.cancelable, (n = document.createEvent("CustomEvent")).initCustomEvent(e, t.bubbles, t.cancelable, t.detail), r = n.preventDefault, n.preventDefault = function() {
+                    r.call(this);
+                    try {
+                        Object.defineProperty(this, "defaultPrevented", {
+                            get: function() {
+                                return !0
+                            }
+                        })
+                    } catch (e) {
+                        this.defaultPrevented = !0
+                    }
+                }, n
+            };
+            t.prototype = window.Event.prototype, window.CustomEvent = t
+        }
+    }()
+}, function(e, t, n) {
+    window.fetch || (window.fetch = n(7).default || n(7))
+}, function(e, i, o) {
+    (function(e) {
+        var t = void 0 !== e && e || "undefined" != typeof self && self || window,
+            n = Function.prototype.apply;
+
+        function r(e, t) {
+            this._id = e, this._clearFn = t
+        }
+        i.setTimeout = function() {
+            return new r(n.call(setTimeout, t, arguments), clearTimeout)
+        }, i.setInterval = function() {
+            return new r(n.call(setInterval, t, arguments), clearInterval)
+        }, i.clearTimeout = i.clearInterval = function(e) {
+            e && e.close()
+        }, r.prototype.unref = r.prototype.ref = function() {}, r.prototype.close = function() {
+            this._clearFn.call(t, this._id)
+        }, i.enroll = function(e, t) {
+            clearTimeout(e._idleTimeoutId), e._idleTimeout = t
+        }, i.unenroll = function(e) {
+            clearTimeout(e._idleTimeoutId), e._idleTimeout = -1
+        }, i._unrefActive = i.active = function(e) {
+            clearTimeout(e._idleTimeoutId);
+            var t = e._idleTimeout;
+            0 <= t && (e._idleTimeoutId = setTimeout(function() {
+                e._onTimeout && e._onTimeout()
+            }, t))
+        }, o(22), i.setImmediate = "undefined" != typeof self && self.setImmediate || void 0 !== e && e.setImmediate || this && this.setImmediate, i.clearImmediate = "undefined" != typeof self && self.clearImmediate || void 0 !== e && e.clearImmediate || this && this.clearImmediate
+    }).call(this, o(4))
+}, function(e, t, n) {
+    (function(e, p) {
+        ! function(n, r) {
+            "use strict";
+            if (!n.setImmediate) {
+                var i, o, t, a, e, s = 1,
+                    c = {},
+                    l = !1,
+                    u = n.document,
+                    f = Object.getPrototypeOf && Object.getPrototypeOf(n);
+                f = f && f.setTimeout ? f : n, i = "[object process]" === {}.toString.call(n.process) ? function(e) {
+                    p.nextTick(function() {
+                        h(e)
+                    })
+                } : function() {
+                    if (n.postMessage && !n.importScripts) {
+                        var e = !0,
+                            t = n.onmessage;
+                        return n.onmessage = function() {
+                            e = !1
+                        }, n.postMessage("", "*"), n.onmessage = t, e
+                    }
+                }() ? (a = "setImmediate$" + Math.random() + "$", e = function(e) {
+                    e.source === n && "string" == typeof e.data && 0 === e.data.indexOf(a) && h(+e.data.slice(a.length))
+                }, n.addEventListener ? n.addEventListener("message", e, !1) : n.attachEvent("onmessage", e), function(e) {
+                    n.postMessage(a + e, "*")
+                }) : n.MessageChannel ? ((t = new MessageChannel).port1.onmessage = function(e) {
+                    h(e.data)
+                }, function(e) {
+                    t.port2.postMessage(e)
+                }) : u && "onreadystatechange" in u.createElement("script") ? (o = u.documentElement, function(e) {
+                    var t = u.createElement("script");
+                    t.onreadystatechange = function() {
+                        h(e), t.onreadystatechange = null, o.removeChild(t), t = null
+                    }, o.appendChild(t)
+                }) : function(e) {
+                    setTimeout(h, 0, e)
+                }, f.setImmediate = function(e) {
+                    "function" != typeof e && (e = new Function("" + e));
+                    for (var t = new Array(arguments.length - 1), n = 0; n < t.length; n++) t[n] = arguments[n + 1];
+                    var r = {
+                        callback: e,
+                        args: t
+                    };
+                    return c[s] = r, i(s), s++
+                }, f.clearImmediate = d
+            }
+
+            function d(e) {
+                delete c[e]
+            }
+
+            function h(e) {
+                if (l) setTimeout(h, 0, e);
+                else {
+                    var t = c[e];
+                    if (t) {
+                        l = !0;
+                        try {
+                            ! function(e) {
+                                var t = e.callback,
+                                    n = e.args;
+                                switch (n.length) {
+                                    case 0:
+                                        t();
+                                        break;
+                                    case 1:
+                                        t(n[0]);
+                                        break;
+                                    case 2:
+                                        t(n[0], n[1]);
+                                        break;
+                                    case 3:
+                                        t(n[0], n[1], n[2]);
+                                        break;
+                                    default:
+                                        t.apply(r, n)
+                                }
+                            }(t)
+                        } finally {
+                            d(e), l = !1
+                        }
+                    }
+                }
+            }
+        }("undefined" == typeof self ? void 0 === e ? this : e : self)
+    }).call(this, n(4), n(23))
+}, function(e, t) {
+    var n, r, i = e.exports = {};
+
+    function o() {
+        throw new Error("setTimeout has not been defined")
+    }
+
+    function a() {
+        throw new Error("clearTimeout has not been defined")
+    }
+
+    function s(t) {
+        if (n === setTimeout) return setTimeout(t, 0);
+        if ((n === o || !n) && setTimeout) return n = setTimeout, setTimeout(t, 0);
+        try {
+            return n(t, 0)
+        } catch (e) {
+            try {
+                return n.call(null, t, 0)
+            } catch (e) {
+                return n.call(this, t, 0)
+            }
+        }
+    }! function() {
+        try {
+            n = "function" == typeof setTimeout ? setTimeout : o
+        } catch (e) {
+            n = o
+        }
+        try {
+            r = "function" == typeof clearTimeout ? clearTimeout : a
+        } catch (e) {
+            r = a
+        }
+    }();
+    var c, l = [],
+        u = !1,
+        f = -1;
+
+    function d() {
+        u && c && (u = !1, c.length ? l = c.concat(l) : f = -1, l.length && h())
+    }
+
+    function h() {
+        if (!u) {
+            var e = s(d);
+            u = !0;
+            for (var t = l.length; t;) {
+                for (c = l, l = []; ++f < t;) c && c[f].run();
+                f = -1, t = l.length
+            }
+            c = null, u = !1,
+                function(t) {
+                    if (r === clearTimeout) return clearTimeout(t);
+                    if ((r === a || !r) && clearTimeout) return r = clearTimeout, clearTimeout(t);
+                    try {
+                        r(t)
+                    } catch (e) {
+                        try {
+                            return r.call(null, t)
+                        } catch (e) {
+                            return r.call(this, t)
+                        }
+                    }
+                }(e)
+        }
+    }
+
+    function p(e, t) {
+        this.fun = e, this.array = t
+    }
+
+    function m() {}
+    i.nextTick = function(e) {
+        var t = new Array(arguments.length - 1);
+        if (1 < arguments.length)
+            for (var n = 1; n < arguments.length; n++) t[n - 1] = arguments[n];
+        l.push(new p(e, t)), 1 !== l.length || u || s(h)
+    }, p.prototype.run = function() {
+        this.fun.apply(null, this.array)
+    }, i.title = "browser", i.browser = !0, i.env = {}, i.argv = [], i.version = "", i.versions = {}, i.on = m, i.addListener = m, i.once = m, i.off = m, i.removeListener = m, i.removeAllListeners = m, i.emit = m, i.prependListener = m, i.prependOnceListener = m, i.listeners = function(e) {
+        return []
+    }, i.binding = function(e) {
+        throw new Error("process.binding is not supported")
+    }, i.cwd = function() {
+        return "/"
+    }, i.chdir = function(e) {
+        throw new Error("process.chdir is not supported")
+    }, i.umask = function() {
+        return 0
+    }
+}, function(i, o, a) {
+    var s, c;
+    /**
+     * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.6
+     * Copyright (C) 2019 Oliver Nightingale
+     * @license MIT
+     */
+    ! function() {
+        var t, l, u, e, n, f, d, h, p, m, y, v, g, w, _, E, x, b, k, S, T, L, R, O, C, r, D = function(e) {
+            var t = new D.Builder;
+            return t.pipeline.add(D.trimmer, D.stopWordFilter, D.stemmer), t.searchPipeline.add(D.stemmer), e.call(t, t), t.build()
+        };
+        D.version = "2.3.6", D.utils = {}, D.utils.warn = (t = this, function(e) {
+            t.console && console.warn && console.warn(e)
+        }), D.utils.asString = function(e) {
+            return null == e ? "" : e.toString()
+        }, D.utils.clone = function(e) {
+            if (null == e) return e;
+            for (var t = Object.create(null), n = Object.keys(e), r = 0; r < n.length; r++) {
+                var i = n[r],
+                    o = e[i];
+                if (Array.isArray(o)) t[i] = o.slice();
+                else {
+                    if ("string" != typeof o && "number" != typeof o && "boolean" != typeof o) throw new TypeError("clone is not deep and does not support nested objects");
+                    t[i] = o
+                }
+            }
+            return t
+        }, D.FieldRef = function(e, t, n) {
+            this.docRef = e, this.fieldName = t, this._stringValue = n
+        }, D.FieldRef.joiner = "/", D.FieldRef.fromString = function(e) {
+            var t = e.indexOf(D.FieldRef.joiner);
+            if (-1 === t) throw "malformed field ref string";
+            var n = e.slice(0, t),
+                r = e.slice(t + 1);
+            return new D.FieldRef(r, n, e)
+        }, D.FieldRef.prototype.toString = function() {
+            return null == this._stringValue && (this._stringValue = this.fieldName + D.FieldRef.joiner + this.docRef), this._stringValue
+        }, D.Set = function(e) {
+            if (this.elements = Object.create(null), e) {
+                this.length = e.length;
+                for (var t = 0; t < this.length; t++) this.elements[e[t]] = !0
+            } else this.length = 0
+        }, D.Set.complete = {
+            intersect: function(e) {
+                return e
+            },
+            union: function(e) {
+                return e
+            },
+            contains: function() {
+                return !0
+            }
+        }, D.Set.empty = {
+            intersect: function() {
+                return this
+            },
+            union: function(e) {
+                return e
+            },
+            contains: function() {
+                return !1
+            }
+        }, D.Set.prototype.contains = function(e) {
+            return !!this.elements[e]
+        }, D.Set.prototype.intersect = function(e) {
+            var t, n, r, i = [];
+            if (e === D.Set.complete) return this;
+            if (e === D.Set.empty) return e;
+            n = this.length < e.length ? (t = this, e) : (t = e, this), r = Object.keys(t.elements);
+            for (var o = 0; o < r.length; o++) {
+                var a = r[o];
+                a in n.elements && i.push(a)
+            }
+            return new D.Set(i)
+        }, D.Set.prototype.union = function(e) {
+            return e === D.Set.complete ? D.Set.complete : e === D.Set.empty ? this : new D.Set(Object.keys(this.elements).concat(Object.keys(e.elements)))
+        }, D.idf = function(e, t) {
+            var n = 0;
+            for (var r in e) "_index" != r && (n += Object.keys(e[r]).length);
+            var i = (t - n + .5) / (n + .5);
+            return Math.log(1 + Math.abs(i))
+        }, D.Token = function(e, t) {
+            this.str = e || "", this.metadata = t || {}
+        }, D.Token.prototype.toString = function() {
+            return this.str
+        }, D.Token.prototype.update = function(e) {
+            return this.str = e(this.str, this.metadata), this
+        }, D.Token.prototype.clone = function(e) {
+            return e = e || function(e) {
+                return e
+            }, new D.Token(e(this.str, this.metadata), this.metadata)
+        }, D.tokenizer = function(e, t) {
+            if (null == e || null == e) return [];
+            if (Array.isArray(e)) return e.map(function(e) {
+                return new D.Token(D.utils.asString(e).toLowerCase(), D.utils.clone(t))
+            });
+            for (var n = e.toString().trim().toLowerCase(), r = n.length, i = [], o = 0, a = 0; o <= r; o++) {
+                var s = o - a;
+                if (n.charAt(o).match(D.tokenizer.separator) || o == r) {
+                    if (0 < s) {
+                        var c = D.utils.clone(t) || {};
+                        c.position = [a, s], c.index = i.length, i.push(new D.Token(n.slice(a, o), c))
+                    }
+                    a = o + 1
+                }
+            }
+            return i
+        }, D.tokenizer.separator = /[\s\-]+/, D.Pipeline = function() {
+            this._stack = []
+        }, D.Pipeline.registeredFunctions = Object.create(null), D.Pipeline.registerFunction = function(e, t) {
+            t in this.registeredFunctions && D.utils.warn("Overwriting existing registered function: " + t), e.label = t, D.Pipeline.registeredFunctions[e.label] = e
+        }, D.Pipeline.warnIfFunctionNotRegistered = function(e) {
+            e.label && e.label in this.registeredFunctions || D.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n", e)
+        }, D.Pipeline.load = function(e) {
+            var n = new D.Pipeline;
+            return e.forEach(function(e) {
+                var t = D.Pipeline.registeredFunctions[e];
+                if (!t) throw new Error("Cannot load unregistered function: " + e);
+                n.add(t)
+            }), n
+        }, D.Pipeline.prototype.add = function() {
+            Array.prototype.slice.call(arguments).forEach(function(e) {
+                D.Pipeline.warnIfFunctionNotRegistered(e), this._stack.push(e)
+            }, this)
+        }, D.Pipeline.prototype.after = function(e, t) {
+            D.Pipeline.warnIfFunctionNotRegistered(t);
+            var n = this._stack.indexOf(e);
+            if (-1 == n) throw new Error("Cannot find existingFn");
+            n += 1, this._stack.splice(n, 0, t)
+        }, D.Pipeline.prototype.before = function(e, t) {
+            D.Pipeline.warnIfFunctionNotRegistered(t);
+            var n = this._stack.indexOf(e);
+            if (-1 == n) throw new Error("Cannot find existingFn");
+            this._stack.splice(n, 0, t)
+        }, D.Pipeline.prototype.remove = function(e) {
+            var t = this._stack.indexOf(e); - 1 != t && this._stack.splice(t, 1)
+        }, D.Pipeline.prototype.run = function(e) {
+            for (var t = this._stack.length, n = 0; n < t; n++) {
+                for (var r = this._stack[n], i = [], o = 0; o < e.length; o++) {
+                    var a = r(e[o], o, e);
+                    if (void 0 !== a && "" !== a)
+                        if (Array.isArray(a))
+                            for (var s = 0; s < a.length; s++) i.push(a[s]);
+                        else i.push(a)
+                }
+                e = i
+            }
+            return e
+        }, D.Pipeline.prototype.runString = function(e, t) {
+            var n = new D.Token(e, t);
+            return this.run([n]).map(function(e) {
+                return e.toString()
+            })
+        }, D.Pipeline.prototype.reset = function() {
+            this._stack = []
+        }, D.Pipeline.prototype.toJSON = function() {
+            return this._stack.map(function(e) {
+                return D.Pipeline.warnIfFunctionNotRegistered(e), e.label
+            })
+        }, D.Vector = function(e) {
+            this._magnitude = 0, this.elements = e || []
+        }, D.Vector.prototype.positionForIndex = function(e) {
+            if (0 == this.elements.length) return 0;
+            for (var t = 0, n = this.elements.length / 2, r = n - t, i = Math.floor(r / 2), o = this.elements[2 * i]; 1 < r && (o < e && (t = i), e < o && (n = i), o != e);) r = n - t, i = t + Math.floor(r / 2), o = this.elements[2 * i];
+            return o == e ? 2 * i : e < o ? 2 * i : o < e ? 2 * (i + 1) : void 0
+        }, D.Vector.prototype.insert = function(e, t) {
+            this.upsert(e, t, function() {
+                throw "duplicate index"
+            })
+        }, D.Vector.prototype.upsert = function(e, t, n) {
+            this._magnitude = 0;
+            var r = this.positionForIndex(e);
+            this.elements[r] == e ? this.elements[r + 1] = n(this.elements[r + 1], t) : this.elements.splice(r, 0, e, t)
+        }, D.Vector.prototype.magnitude = function() {
+            if (this._magnitude) return this._magnitude;
+            for (var e = 0, t = this.elements.length, n = 1; n < t; n += 2) {
+                var r = this.elements[n];
+                e += r * r
+            }
+            return this._magnitude = Math.sqrt(e)
+        }, D.Vector.prototype.dot = function(e) {
+            for (var t = 0, n = this.elements, r = e.elements, i = n.length, o = r.length, a = 0, s = 0, c = 0, l = 0; c < i && l < o;)(a = n[c]) < (s = r[l]) ? c += 2 : s < a ? l += 2 : a == s && (t += n[c + 1] * r[l + 1], c += 2, l += 2);
+            return t
+        }, D.Vector.prototype.similarity = function(e) {
+            return this.dot(e) / this.magnitude() || 0
+        }, D.Vector.prototype.toArray = function() {
+            for (var e = new Array(this.elements.length / 2), t = 1, n = 0; t < this.elements.length; t += 2, n++) e[n] = this.elements[t];
+            return e
+        }, D.Vector.prototype.toJSON = function() {
+            return this.elements
+        }, D.stemmer = (l = {
+            ational: "ate",
+            tional: "tion",
+            enci: "ence",
+            anci: "ance",
+            izer: "ize",
+            bli: "ble",
+            alli: "al",
+            entli: "ent",
+            eli: "e",
+            ousli: "ous",
+            ization: "ize",
+            ation: "ate",
+            ator: "ate",
+            alism: "al",
+            iveness: "ive",
+            fulness: "ful",
+            ousness: "ous",
+            aliti: "al",
+            iviti: "ive",
+            biliti: "ble",
+            logi: "log"
+        }, u = {
+            icate: "ic",
+            ative: "",
+            alize: "al",
+            iciti: "ic",
+            ical: "ic",
+            ful: "",
+            ness: ""
+        }, e = "[aeiouy]", n = "[^aeiou][^aeiouy]*", f = new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*"), d = new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*"), h = new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*([aeiouy][aeiou]*)?$"), p = new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy]"), m = /^(.+?)(ss|i)es$/, y = /^(.+?)([^s])s$/, v = /^(.+?)eed$/, g = /^(.+?)(ed|ing)$/, w = /.$/, _ = /(at|bl|iz)$/, E = new RegExp("([^aeiouylsz])\\1$"), x = new RegExp("^" + n + e + "[^aeiouwxy]$"), b = /^(.+?[^aeiou])y$/, k = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/, S = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/, T = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/, L = /^(.+?)(s|t)(ion)$/, R = /^(.+?)e$/, O = /ll$/, C = new RegExp("^" + n + e + "[^aeiouwxy]$"), r = function(e) {
+            var t, n, r, i, o, a, s;
+            if (e.length < 3) return e;
+            if ("y" == (r = e.substr(0, 1)) && (e = r.toUpperCase() + e.substr(1)), o = y, (i = m).test(e) ? e = e.replace(i, "$1$2") : o.test(e) && (e = e.replace(o, "$1$2")), o = g, (i = v).test(e)) {
+                var c = i.exec(e);
+                (i = f).test(c[1]) && (i = w, e = e.replace(i, ""))
+            } else if (o.test(e)) {
+                t = (c = o.exec(e))[1], (o = p).test(t) && (a = E, s = x, (o = _).test(e = t) ? e += "e" : a.test(e) ? (i = w, e = e.replace(i, "")) : s.test(e) && (e += "e"))
+            }(i = b).test(e) && (e = (t = (c = i.exec(e))[1]) + "i");
+            (i = k).test(e) && (t = (c = i.exec(e))[1], n = c[2], (i = f).test(t) && (e = t + l[n]));
+            (i = S).test(e) && (t = (c = i.exec(e))[1], n = c[2], (i = f).test(t) && (e = t + u[n]));
+            if (o = L, (i = T).test(e)) t = (c = i.exec(e))[1], (i = d).test(t) && (e = t);
+            else if (o.test(e)) {
+                t = (c = o.exec(e))[1] + c[2], (o = d).test(t) && (e = t)
+            }(i = R).test(e) && (t = (c = i.exec(e))[1], o = h, a = C, ((i = d).test(t) || o.test(t) && !a.test(t)) && (e = t));
+            return o = d, (i = O).test(e) && o.test(e) && (i = w, e = e.replace(i, "")), "y" == r && (e = r.toLowerCase() + e.substr(1)), e
+        }, function(e) {
+            return e.update(r)
+        }), D.Pipeline.registerFunction(D.stemmer, "stemmer"), D.generateStopWordFilter = function(e) {
+            var t = e.reduce(function(e, t) {
+                return e[t] = t, e
+            }, {});
+            return function(e) {
+                if (e && t[e.toString()] !== e.toString()) return e
+            }
+        }, D.stopWordFilter = D.generateStopWordFilter(["a", "able", "about", "across", "after", "all", "almost", "also", "am", "among", "an", "and", "any", "are", "as", "at", "be", "because", "been", "but", "by", "can", "cannot", "could", "dear", "did", "do", "does", "either", "else", "ever", "every", "for", "from", "get", "got", "had", "has", "have", "he", "her", "hers", "him", "his", "how", "however", "i", "if", "in", "into", "is", "it", "its", "just", "least", "let", "like", "likely", "may", "me", "might", "most", "must", "my", "neither", "no", "nor", "not", "of", "off", "often", "on", "only", "or", "other", "our", "own", "rather", "said", "say", "says", "she", "should", "since", "so", "some", "than", "that", "the", "their", "them", "then", "there", "these", "they", "this", "tis", "to", "too", "twas", "us", "wants", "was", "we", "were", "what", "when", "where", "which", "while", "who", "whom", "why", "will", "with", "would", "yet", "you", "your"]), D.Pipeline.registerFunction(D.stopWordFilter, "stopWordFilter"), D.trimmer = function(e) {
+            return e.update(function(e) {
+                return e.replace(/^\W+/, "").replace(/\W+$/, "")
+            })
+        }, D.Pipeline.registerFunction(D.trimmer, "trimmer"), D.TokenSet = function() {
+            this.final = !1, this.edges = {}, this.id = D.TokenSet._nextId, D.TokenSet._nextId += 1
+        }, D.TokenSet._nextId = 1, D.TokenSet.fromArray = function(e) {
+            for (var t = new D.TokenSet.Builder, n = 0, r = e.length; n < r; n++) t.insert(e[n]);
+            return t.finish(), t.root
+        }, D.TokenSet.fromClause = function(e) {
+            return "editDistance" in e ? D.TokenSet.fromFuzzyString(e.term, e.editDistance) : D.TokenSet.fromString(e.term)
+        }, D.TokenSet.fromFuzzyString = function(e, t) {
+            for (var n = new D.TokenSet, r = [{
+                    node: n,
+                    editsRemaining: t,
+                    str: e
+                }]; r.length;) {
+                var i = r.pop();
+                if (0 < i.str.length) {
+                    var o, a = i.str.charAt(0);
+                    a in i.node.edges ? o = i.node.edges[a] : (o = new D.TokenSet, i.node.edges[a] = o), 1 == i.str.length && (o.final = !0), r.push({
+                        node: o,
+                        editsRemaining: i.editsRemaining,
+                        str: i.str.slice(1)
+                    })
+                }
+                if (0 != i.editsRemaining) {
+                    if ("*" in i.node.edges) var s = i.node.edges["*"];
+                    else {
+                        s = new D.TokenSet;
+                        i.node.edges["*"] = s
+                    }
+                    if (0 == i.str.length && (s.final = !0), r.push({
+                            node: s,
+                            editsRemaining: i.editsRemaining - 1,
+                            str: i.str
+                        }), 1 < i.str.length && r.push({
+                            node: i.node,
+                            editsRemaining: i.editsRemaining - 1,
+                            str: i.str.slice(1)
+                        }), 1 == i.str.length && (i.node.final = !0), 1 <= i.str.length) {
+                        if ("*" in i.node.edges) var c = i.node.edges["*"];
+                        else {
+                            c = new D.TokenSet;
+                            i.node.edges["*"] = c
+                        }
+                        1 == i.str.length && (c.final = !0), r.push({
+                            node: c,
+                            editsRemaining: i.editsRemaining - 1,
+                            str: i.str.slice(1)
+                        })
+                    }
+                    if (1 < i.str.length) {
+                        var l, u = i.str.charAt(0),
+                            f = i.str.charAt(1);
+                        f in i.node.edges ? l = i.node.edges[f] : (l = new D.TokenSet, i.node.edges[f] = l), 1 == i.str.length && (l.final = !0), r.push({
+                            node: l,
+                            editsRemaining: i.editsRemaining - 1,
+                            str: u + i.str.slice(2)
+                        })
+                    }
+                }
+            }
+            return n
+        }, D.TokenSet.fromString = function(e) {
+            for (var t = new D.TokenSet, n = t, r = 0, i = e.length; r < i; r++) {
+                var o = e[r],
+                    a = r == i - 1;
+                if ("*" == o)(t.edges[o] = t).final = a;
+                else {
+                    var s = new D.TokenSet;
+                    s.final = a, t.edges[o] = s, t = s
+                }
+            }
+            return n
+        }, D.TokenSet.prototype.toArray = function() {
+            for (var e = [], t = [{
+                    prefix: "",
+                    node: this
+                }]; t.length;) {
+                var n = t.pop(),
+                    r = Object.keys(n.node.edges),
+                    i = r.length;
+                n.node.final && (n.prefix.charAt(0), e.push(n.prefix));
+                for (var o = 0; o < i; o++) {
+                    var a = r[o];
+                    t.push({
+                        prefix: n.prefix.concat(a),
+                        node: n.node.edges[a]
+                    })
+                }
+            }
+            return e
+        }, D.TokenSet.prototype.toString = function() {
+            if (this._str) return this._str;
+            for (var e = this.final ? "1" : "0", t = Object.keys(this.edges).sort(), n = t.length, r = 0; r < n; r++) {
+                var i = t[r];
+                e = e + i + this.edges[i].id
+            }
+            return e
+        }, D.TokenSet.prototype.intersect = function(e) {
+            for (var t = new D.TokenSet, n = void 0, r = [{
+                    qNode: e,
+                    output: t,
+                    node: this
+                }]; r.length;) {
+                n = r.pop();
+                for (var i = Object.keys(n.qNode.edges), o = i.length, a = Object.keys(n.node.edges), s = a.length, c = 0; c < o; c++)
+                    for (var l = i[c], u = 0; u < s; u++) {
+                        var f = a[u];
+                        if (f == l || "*" == l) {
+                            var d = n.node.edges[f],
+                                h = n.qNode.edges[l],
+                                p = d.final && h.final,
+                                m = void 0;
+                            f in n.output.edges ? (m = n.output.edges[f]).final = m.final || p : ((m = new D.TokenSet).final = p, n.output.edges[f] = m), r.push({
+                                qNode: h,
+                                output: m,
+                                node: d
+                            })
+                        }
+                    }
+            }
+            return t
+        }, D.TokenSet.Builder = function() {
+            this.previousWord = "", this.root = new D.TokenSet, this.uncheckedNodes = [], this.minimizedNodes = {}
+        }, D.TokenSet.Builder.prototype.insert = function(e) {
+            var t, n = 0;
+            if (e < this.previousWord) throw new Error("Out of order word insertion");
+            for (var r = 0; r < e.length && r < this.previousWord.length && e[r] == this.previousWord[r]; r++) n++;
+            this.minimize(n), t = 0 == this.uncheckedNodes.length ? this.root : this.uncheckedNodes[this.uncheckedNodes.length - 1].child;
+            for (r = n; r < e.length; r++) {
+                var i = new D.TokenSet,
+                    o = e[r];
+                t.edges[o] = i, this.uncheckedNodes.push({
+                    parent: t,
+                    char: o,
+                    child: i
+                }), t = i
+            }
+            t.final = !0, this.previousWord = e
+        }, D.TokenSet.Builder.prototype.finish = function() {
+            this.minimize(0)
+        }, D.TokenSet.Builder.prototype.minimize = function(e) {
+            for (var t = this.uncheckedNodes.length - 1; e <= t; t--) {
+                var n = this.uncheckedNodes[t],
+                    r = n.child.toString();
+                r in this.minimizedNodes ? n.parent.edges[n.char] = this.minimizedNodes[r] : (n.child._str = r, this.minimizedNodes[r] = n.child), this.uncheckedNodes.pop()
+            }
+        }, D.Index = function(e) {
+            this.invertedIndex = e.invertedIndex, this.fieldVectors = e.fieldVectors, this.tokenSet = e.tokenSet, this.fields = e.fields, this.pipeline = e.pipeline
+        }, D.Index.prototype.search = function(t) {
+            return this.query(function(e) {
+                new D.QueryParser(t, e).parse()
+            })
+        }, D.Index.prototype.query = function(e) {
+            for (var t = new D.Query(this.fields), n = Object.create(null), r = Object.create(null), i = Object.create(null), o = Object.create(null), a = Object.create(null), s = 0; s < this.fields.length; s++) r[this.fields[s]] = new D.Vector;
+            e.call(t, t);
+            for (s = 0; s < t.clauses.length; s++) {
+                var c = t.clauses[s],
+                    l = null,
+                    u = D.Set.complete;
+                l = c.usePipeline ? this.pipeline.runString(c.term, {
+                    fields: c.fields
+                }) : [c.term];
+                for (var f = 0; f < l.length; f++) {
+                    var d = l[f];
+                    c.term = d;
+                    var h = D.TokenSet.fromClause(c),
+                        p = this.tokenSet.intersect(h).toArray();
+                    if (0 === p.length && c.presence === D.Query.presence.REQUIRED) {
+                        for (var m = 0; m < c.fields.length; m++) {
+                            o[Q = c.fields[m]] = D.Set.empty
+                        }
+                        break
+                    }
+                    for (var y = 0; y < p.length; y++) {
+                        var v = p[y],
+                            g = this.invertedIndex[v],
+                            w = g._index;
+                        for (m = 0; m < c.fields.length; m++) {
+                            var _ = g[Q = c.fields[m]],
+                                E = Object.keys(_),
+                                x = v + "/" + Q,
+                                b = new D.Set(E);
+                            if (c.presence == D.Query.presence.REQUIRED && (u = u.union(b), void 0 === o[Q] && (o[Q] = D.Set.complete)), c.presence != D.Query.presence.PROHIBITED) {
+                                if (r[Q].upsert(w, c.boost, function(e, t) {
+                                        return e + t
+                                    }), !i[x]) {
+                                    for (var k = 0; k < E.length; k++) {
+                                        var S, T = E[k],
+                                            L = new D.FieldRef(T, Q),
+                                            R = _[T];
+                                        void 0 === (S = n[L]) ? n[L] = new D.MatchData(v, Q, R) : S.add(v, Q, R)
+                                    }
+                                    i[x] = !0
+                                }
+                            } else void 0 === a[Q] && (a[Q] = D.Set.empty), a[Q] = a[Q].union(b)
+                        }
+                    }
+                }
+                if (c.presence === D.Query.presence.REQUIRED)
+                    for (m = 0; m < c.fields.length; m++) {
+                        o[Q = c.fields[m]] = o[Q].intersect(u)
+                    }
+            }
+            var O = D.Set.complete,
+                C = D.Set.empty;
+            for (s = 0; s < this.fields.length; s++) {
+                var Q;
+                o[Q = this.fields[s]] && (O = O.intersect(o[Q])), a[Q] && (C = C.union(a[Q]))
+            }
+            var P = Object.keys(n),
+                A = [],
+                I = Object.create(null);
+            if (t.isNegated()) {
+                P = Object.keys(this.fieldVectors);
+                for (s = 0; s < P.length; s++) {
+                    L = P[s];
+                    var M = D.FieldRef.fromString(L);
+                    n[L] = new D.MatchData
+                }
+            }
+            for (s = 0; s < P.length; s++) {
+                var N = (M = D.FieldRef.fromString(P[s])).docRef;
+                if (O.contains(N) && !C.contains(N)) {
+                    var j, F = this.fieldVectors[M],
+                        H = r[M.fieldName].similarity(F);
+                    if (void 0 !== (j = I[N])) j.score += H, j.matchData.combine(n[M]);
+                    else {
+                        var q = {
+                            ref: N,
+                            score: H,
+                            matchData: n[M]
+                        };
+                        I[N] = q, A.push(q)
+                    }
+                }
+            }
+            return A.sort(function(e, t) {
+                return t.score - e.score
+            })
+        }, D.Index.prototype.toJSON = function() {
+            var e = Object.keys(this.invertedIndex).sort().map(function(e) {
+                    return [e, this.invertedIndex[e]]
+                }, this),
+                t = Object.keys(this.fieldVectors).map(function(e) {
+                    return [e, this.fieldVectors[e].toJSON()]
+                }, this);
+            return {
+                version: D.version,
+                fields: this.fields,
+                fieldVectors: t,
+                invertedIndex: e,
+                pipeline: this.pipeline.toJSON()
+            }
+        }, D.Index.load = function(e) {
+            var t = {},
+                n = {},
+                r = e.fieldVectors,
+                i = Object.create(null),
+                o = e.invertedIndex,
+                a = new D.TokenSet.Builder,
+                s = D.Pipeline.load(e.pipeline);
+            e.version != D.version && D.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + D.version + "' does not match serialized index '" + e.version + "'");
+            for (var c = 0; c < r.length; c++) {
+                var l = (f = r[c])[0],
+                    u = f[1];
+                n[l] = new D.Vector(u)
+            }
+            for (c = 0; c < o.length; c++) {
+                var f, d = (f = o[c])[0],
+                    h = f[1];
+                a.insert(d), i[d] = h
+            }
+            return a.finish(), t.fields = e.fields, t.fieldVectors = n, t.invertedIndex = i, t.tokenSet = a.root, t.pipeline = s, new D.Index(t)
+        }, D.Builder = function() {
+            this._ref = "id", this._fields = Object.create(null), this._documents = Object.create(null), this.invertedIndex = Object.create(null), this.fieldTermFrequencies = {}, this.fieldLengths = {}, this.tokenizer = D.tokenizer, this.pipeline = new D.Pipeline, this.searchPipeline = new D.Pipeline, this.documentCount = 0, this._b = .75, this._k1 = 1.2, this.termIndex = 0, this.metadataWhitelist = []
+        }, D.Builder.prototype.ref = function(e) {
+            this._ref = e
+        }, D.Builder.prototype.field = function(e, t) {
+            if (/\//.test(e)) throw new RangeError("Field '" + e + "' contains illegal character '/'");
+            this._fields[e] = t || {}
+        }, D.Builder.prototype.b = function(e) {
+            this._b = e < 0 ? 0 : 1 < e ? 1 : e
+        }, D.Builder.prototype.k1 = function(e) {
+            this._k1 = e
+        }, D.Builder.prototype.add = function(e, t) {
+            var n = e[this._ref],
+                r = Object.keys(this._fields);
+            this._documents[n] = t || {}, this.documentCount += 1;
+            for (var i = 0; i < r.length; i++) {
+                var o = r[i],
+                    a = this._fields[o].extractor,
+                    s = a ? a(e) : e[o],
+                    c = this.tokenizer(s, {
+                        fields: [o]
+                    }),
+                    l = this.pipeline.run(c),
+                    u = new D.FieldRef(n, o),
+                    f = Object.create(null);
+                this.fieldTermFrequencies[u] = f, this.fieldLengths[u] = 0, this.fieldLengths[u] += l.length;
+                for (var d = 0; d < l.length; d++) {
+                    var h = l[d];
+                    if (null == f[h] && (f[h] = 0), f[h] += 1, null == this.invertedIndex[h]) {
+                        var p = Object.create(null);
+                        p._index = this.termIndex, this.termIndex += 1;
+                        for (var m = 0; m < r.length; m++) p[r[m]] = Object.create(null);
+                        this.invertedIndex[h] = p
+                    }
+                    null == this.invertedIndex[h][o][n] && (this.invertedIndex[h][o][n] = Object.create(null));
+                    for (var y = 0; y < this.metadataWhitelist.length; y++) {
+                        var v = this.metadataWhitelist[y],
+                            g = h.metadata[v];
+                        null == this.invertedIndex[h][o][n][v] && (this.invertedIndex[h][o][n][v] = []), this.invertedIndex[h][o][n][v].push(g)
+                    }
+                }
+            }
+        }, D.Builder.prototype.calculateAverageFieldLengths = function() {
+            for (var e = Object.keys(this.fieldLengths), t = e.length, n = {}, r = {}, i = 0; i < t; i++) {
+                var o = D.FieldRef.fromString(e[i]),
+                    a = o.fieldName;
+                r[a] || (r[a] = 0), r[a] += 1, n[a] || (n[a] = 0), n[a] += this.fieldLengths[o]
+            }
+            var s = Object.keys(this._fields);
+            for (i = 0; i < s.length; i++) {
+                var c = s[i];
+                n[c] = n[c] / r[c]
+            }
+            this.averageFieldLength = n
+        }, D.Builder.prototype.createFieldVectors = function() {
+            for (var e = {}, t = Object.keys(this.fieldTermFrequencies), n = t.length, r = Object.create(null), i = 0; i < n; i++) {
+                for (var o = D.FieldRef.fromString(t[i]), a = o.fieldName, s = this.fieldLengths[o], c = new D.Vector, l = this.fieldTermFrequencies[o], u = Object.keys(l), f = u.length, d = this._fields[a].boost || 1, h = this._documents[o.docRef].boost || 1, p = 0; p < f; p++) {
+                    var m, y, v, g = u[p],
+                        w = l[g],
+                        _ = this.invertedIndex[g]._index;
+                    void 0 === r[g] ? (m = D.idf(this.invertedIndex[g], this.documentCount), r[g] = m) : m = r[g], y = m * ((this._k1 + 1) * w) / (this._k1 * (1 - this._b + this._b * (s / this.averageFieldLength[a])) + w), y *= d, y *= h, v = Math.round(1e3 * y) / 1e3, c.insert(_, v)
+                }
+                e[o] = c
+            }
+            this.fieldVectors = e
+        }, D.Builder.prototype.createTokenSet = function() {
+            this.tokenSet = D.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())
+        }, D.Builder.prototype.build = function() {
+            return this.calculateAverageFieldLengths(), this.createFieldVectors(), this.createTokenSet(), new D.Index({
+                invertedIndex: this.invertedIndex,
+                fieldVectors: this.fieldVectors,
+                tokenSet: this.tokenSet,
+                fields: Object.keys(this._fields),
+                pipeline: this.searchPipeline
+            })
+        }, D.Builder.prototype.use = function(e) {
+            var t = Array.prototype.slice.call(arguments, 1);
+            t.unshift(this), e.apply(this, t)
+        }, D.MatchData = function(e, t, n) {
+            for (var r = Object.create(null), i = Object.keys(n || {}), o = 0; o < i.length; o++) {
+                var a = i[o];
+                r[a] = n[a].slice()
+            }
+            this.metadata = Object.create(null), void 0 !== e && (this.metadata[e] = Object.create(null), this.metadata[e][t] = r)
+        }, D.MatchData.prototype.combine = function(e) {
+            for (var t = Object.keys(e.metadata), n = 0; n < t.length; n++) {
+                var r = t[n],
+                    i = Object.keys(e.metadata[r]);
+                null == this.metadata[r] && (this.metadata[r] = Object.create(null));
+                for (var o = 0; o < i.length; o++) {
+                    var a = i[o],
+                        s = Object.keys(e.metadata[r][a]);
+                    null == this.metadata[r][a] && (this.metadata[r][a] = Object.create(null));
+                    for (var c = 0; c < s.length; c++) {
+                        var l = s[c];
+                        null == this.metadata[r][a][l] ? this.metadata[r][a][l] = e.metadata[r][a][l] : this.metadata[r][a][l] = this.metadata[r][a][l].concat(e.metadata[r][a][l])
+                    }
+                }
+            }
+        }, D.MatchData.prototype.add = function(e, t, n) {
+            if (!(e in this.metadata)) return this.metadata[e] = Object.create(null), void(this.metadata[e][t] = n);
+            if (t in this.metadata[e])
+                for (var r = Object.keys(n), i = 0; i < r.length; i++) {
+                    var o = r[i];
+                    o in this.metadata[e][t] ? this.metadata[e][t][o] = this.metadata[e][t][o].concat(n[o]) : this.metadata[e][t][o] = n[o]
+                } else this.metadata[e][t] = n
+        }, D.Query = function(e) {
+            this.clauses = [], this.allFields = e
+        }, D.Query.wildcard = new String("*"), D.Query.wildcard.NONE = 0, D.Query.wildcard.LEADING = 1, D.Query.wildcard.TRAILING = 2, D.Query.presence = {
+            OPTIONAL: 1,
+            REQUIRED: 2,
+            PROHIBITED: 3
+        }, D.Query.prototype.clause = function(e) {
+            return "fields" in e || (e.fields = this.allFields), "boost" in e || (e.boost = 1), "usePipeline" in e || (e.usePipeline = !0), "wildcard" in e || (e.wildcard = D.Query.wildcard.NONE), e.wildcard & D.Query.wildcard.LEADING && e.term.charAt(0) != D.Query.wildcard && (e.term = "*" + e.term), e.wildcard & D.Query.wildcard.TRAILING && e.term.slice(-1) != D.Query.wildcard && (e.term = e.term + "*"), "presence" in e || (e.presence = D.Query.presence.OPTIONAL), this.clauses.push(e), this
+        }, D.Query.prototype.isNegated = function() {
+            for (var e = 0; e < this.clauses.length; e++)
+                if (this.clauses[e].presence != D.Query.presence.PROHIBITED) return !1;
+            return !0
+        }, D.Query.prototype.term = function(e, t) {
+            if (Array.isArray(e)) return e.forEach(function(e) {
+                this.term(e, D.utils.clone(t))
+            }, this), this;
+            var n = t || {};
+            return n.term = e.toString(), this.clause(n), this
+        }, D.QueryParseError = function(e, t, n) {
+            this.name = "QueryParseError", this.message = e, this.start = t, this.end = n
+        }, D.QueryParseError.prototype = new Error, D.QueryLexer = function(e) {
+            this.lexemes = [], this.str = e, this.length = e.length, this.pos = 0, this.start = 0, this.escapeCharPositions = []
+        }, D.QueryLexer.prototype.run = function() {
+            for (var e = D.QueryLexer.lexText; e;) e = e(this)
+        }, D.QueryLexer.prototype.sliceString = function() {
+            for (var e = [], t = this.start, n = this.pos, r = 0; r < this.escapeCharPositions.length; r++) n = this.escapeCharPositions[r], e.push(this.str.slice(t, n)), t = n + 1;
+            return e.push(this.str.slice(t, this.pos)), this.escapeCharPositions.length = 0, e.join("")
+        }, D.QueryLexer.prototype.emit = function(e) {
+            this.lexemes.push({
+                type: e,
+                str: this.sliceString(),
+                start: this.start,
+                end: this.pos
+            }), this.start = this.pos
+        }, D.QueryLexer.prototype.escapeCharacter = function() {
+            this.escapeCharPositions.push(this.pos - 1), this.pos += 1
+        }, D.QueryLexer.prototype.next = function() {
+            if (this.pos >= this.length) return D.QueryLexer.EOS;
+            var e = this.str.charAt(this.pos);
+            return this.pos += 1, e
+        }, D.QueryLexer.prototype.width = function() {
+            return this.pos - this.start
+        }, D.QueryLexer.prototype.ignore = function() {
+            this.start == this.pos && (this.pos += 1), this.start = this.pos
+        }, D.QueryLexer.prototype.backup = function() {
+            this.pos -= 1
+        }, D.QueryLexer.prototype.acceptDigitRun = function() {
+            for (var e, t; 47 < (t = (e = this.next()).charCodeAt(0)) && t < 58;);
+            e != D.QueryLexer.EOS && this.backup()
+        }, D.QueryLexer.prototype.more = function() {
+            return this.pos < this.length
+        }, D.QueryLexer.EOS = "EOS", D.QueryLexer.FIELD = "FIELD", D.QueryLexer.TERM = "TERM", D.QueryLexer.EDIT_DISTANCE = "EDIT_DISTANCE", D.QueryLexer.BOOST = "BOOST", D.QueryLexer.PRESENCE = "PRESENCE", D.QueryLexer.lexField = function(e) {
+            return e.backup(), e.emit(D.QueryLexer.FIELD), e.ignore(), D.QueryLexer.lexText
+        }, D.QueryLexer.lexTerm = function(e) {
+            if (1 < e.width() && (e.backup(), e.emit(D.QueryLexer.TERM)), e.ignore(), e.more()) return D.QueryLexer.lexText
+        }, D.QueryLexer.lexEditDistance = function(e) {
+            return e.ignore(), e.acceptDigitRun(), e.emit(D.QueryLexer.EDIT_DISTANCE), D.QueryLexer.lexText
+        }, D.QueryLexer.lexBoost = function(e) {
+            return e.ignore(), e.acceptDigitRun(), e.emit(D.QueryLexer.BOOST), D.QueryLexer.lexText
+        }, D.QueryLexer.lexEOS = function(e) {
+            0 < e.width() && e.emit(D.QueryLexer.TERM)
+        }, D.QueryLexer.termSeparator = D.tokenizer.separator, D.QueryLexer.lexText = function(e) {
+            for (;;) {
+                var t = e.next();
+                if (t == D.QueryLexer.EOS) return D.QueryLexer.lexEOS;
+                if (92 != t.charCodeAt(0)) {
+                    if (":" == t) return D.QueryLexer.lexField;
+                    if ("~" == t) return e.backup(), 0 < e.width() && e.emit(D.QueryLexer.TERM), D.QueryLexer.lexEditDistance;
+                    if ("^" == t) return e.backup(), 0 < e.width() && e.emit(D.QueryLexer.TERM), D.QueryLexer.lexBoost;
+                    if ("+" == t && 1 === e.width()) return e.emit(D.QueryLexer.PRESENCE), D.QueryLexer.lexText;
+                    if ("-" == t && 1 === e.width()) return e.emit(D.QueryLexer.PRESENCE), D.QueryLexer.lexText;
+                    if (t.match(D.QueryLexer.termSeparator)) return D.QueryLexer.lexTerm
+                } else e.escapeCharacter()
+            }
+        }, D.QueryParser = function(e, t) {
+            this.lexer = new D.QueryLexer(e), this.query = t, this.currentClause = {}, this.lexemeIdx = 0
+        }, D.QueryParser.prototype.parse = function() {
+            this.lexer.run(), this.lexemes = this.lexer.lexemes;
+            for (var e = D.QueryParser.parseClause; e;) e = e(this);
+            return this.query
+        }, D.QueryParser.prototype.peekLexeme = function() {
+            return this.lexemes[this.lexemeIdx]
+        }, D.QueryParser.prototype.consumeLexeme = function() {
+            var e = this.peekLexeme();
+            return this.lexemeIdx += 1, e
+        }, D.QueryParser.prototype.nextClause = function() {
+            var e = this.currentClause;
+            this.query.clause(e), this.currentClause = {}
+        }, D.QueryParser.parseClause = function(e) {
+            var t = e.peekLexeme();
+            if (null != t) switch (t.type) {
+                case D.QueryLexer.PRESENCE:
+                    return D.QueryParser.parsePresence;
+                case D.QueryLexer.FIELD:
+                    return D.QueryParser.parseField;
+                case D.QueryLexer.TERM:
+                    return D.QueryParser.parseTerm;
+                default:
+                    var n = "expected either a field or a term, found " + t.type;
+                    throw 1 <= t.str.length && (n += " with value '" + t.str + "'"), new D.QueryParseError(n, t.start, t.end)
+            }
+        }, D.QueryParser.parsePresence = function(e) {
+            var t = e.consumeLexeme();
+            if (null != t) {
+                switch (t.str) {
+                    case "-":
+                        e.currentClause.presence = D.Query.presence.PROHIBITED;
+                        break;
+                    case "+":
+                        e.currentClause.presence = D.Query.presence.REQUIRED;
+                        break;
+                    default:
+                        var n = "unrecognised presence operator'" + t.str + "'";
+                        throw new D.QueryParseError(n, t.start, t.end)
+                }
+                var r = e.peekLexeme();
+                if (null == r) {
+                    n = "expecting term or field, found nothing";
+                    throw new D.QueryParseError(n, t.start, t.end)
+                }
+                switch (r.type) {
+                    case D.QueryLexer.FIELD:
+                        return D.QueryParser.parseField;
+                    case D.QueryLexer.TERM:
+                        return D.QueryParser.parseTerm;
+                    default:
+                        n = "expecting term or field, found '" + r.type + "'";
+                        throw new D.QueryParseError(n, r.start, r.end)
+                }
+            }
+        }, D.QueryParser.parseField = function(e) {
+            var t = e.consumeLexeme();
+            if (null != t) {
+                if (-1 == e.query.allFields.indexOf(t.str)) {
+                    var n = e.query.allFields.map(function(e) {
+                            return "'" + e + "'"
+                        }).join(", "),
+                        r = "unrecognised field '" + t.str + "', possible fields: " + n;
+                    throw new D.QueryParseError(r, t.start, t.end)
+                }
+                e.currentClause.fields = [t.str];
+                var i = e.peekLexeme();
+                if (null == i) {
+                    r = "expecting term, found nothing";
+                    throw new D.QueryParseError(r, t.start, t.end)
+                }
+                switch (i.type) {
+                    case D.QueryLexer.TERM:
+                        return D.QueryParser.parseTerm;
+                    default:
+                        r = "expecting term, found '" + i.type + "'";
+                        throw new D.QueryParseError(r, i.start, i.end)
+                }
+            }
+        }, D.QueryParser.parseTerm = function(e) {
+            var t = e.consumeLexeme();
+            if (null != t) {
+                e.currentClause.term = t.str.toLowerCase(), -1 != t.str.indexOf("*") && (e.currentClause.usePipeline = !1);
+                var n = e.peekLexeme();
+                if (null != n) switch (n.type) {
+                    case D.QueryLexer.TERM:
+                        return e.nextClause(), D.QueryParser.parseTerm;
+                    case D.QueryLexer.FIELD:
+                        return e.nextClause(), D.QueryParser.parseField;
+                    case D.QueryLexer.EDIT_DISTANCE:
+                        return D.QueryParser.parseEditDistance;
+                    case D.QueryLexer.BOOST:
+                        return D.QueryParser.parseBoost;
+                    case D.QueryLexer.PRESENCE:
+                        return e.nextClause(), D.QueryParser.parsePresence;
+                    default:
+                        var r = "Unexpected lexeme type '" + n.type + "'";
+                        throw new D.QueryParseError(r, n.start, n.end)
+                } else e.nextClause()
+            }
+        }, D.QueryParser.parseEditDistance = function(e) {
+            var t = e.consumeLexeme();
+            if (null != t) {
+                var n = parseInt(t.str, 10);
+                if (isNaN(n)) {
+                    var r = "edit distance must be numeric";
+                    throw new D.QueryParseError(r, t.start, t.end)
+                }
+                e.currentClause.editDistance = n;
+                var i = e.peekLexeme();
+                if (null != i) switch (i.type) {
+                    case D.QueryLexer.TERM:
+                        return e.nextClause(), D.QueryParser.parseTerm;
+                    case D.QueryLexer.FIELD:
+                        return e.nextClause(), D.QueryParser.parseField;
+                    case D.QueryLexer.EDIT_DISTANCE:
+                        return D.QueryParser.parseEditDistance;
+                    case D.QueryLexer.BOOST:
+                        return D.QueryParser.parseBoost;
+                    case D.QueryLexer.PRESENCE:
+                        return e.nextClause(), D.QueryParser.parsePresence;
+                    default:
+                        r = "Unexpected lexeme type '" + i.type + "'";
+                        throw new D.QueryParseError(r, i.start, i.end)
+                } else e.nextClause()
+            }
+        }, D.QueryParser.parseBoost = function(e) {
+            var t = e.consumeLexeme();
+            if (null != t) {
+                var n = parseInt(t.str, 10);
+                if (isNaN(n)) {
+                    var r = "boost must be numeric";
+                    throw new D.QueryParseError(r, t.start, t.end)
+                }
+                e.currentClause.boost = n;
+                var i = e.peekLexeme();
+                if (null != i) switch (i.type) {
+                    case D.QueryLexer.TERM:
+                        return e.nextClause(), D.QueryParser.parseTerm;
+                    case D.QueryLexer.FIELD:
+                        return e.nextClause(), D.QueryParser.parseField;
+                    case D.QueryLexer.EDIT_DISTANCE:
+                        return D.QueryParser.parseEditDistance;
+                    case D.QueryLexer.BOOST:
+                        return D.QueryParser.parseBoost;
+                    case D.QueryLexer.PRESENCE:
+                        return e.nextClause(), D.QueryParser.parsePresence;
+                    default:
+                        r = "Unexpected lexeme type '" + i.type + "'";
+                        throw new D.QueryParseError(r, i.start, i.end)
+                } else e.nextClause()
+            }
+        }, void 0 === (c = "function" == typeof(s = function() {
+            return D
+        }) ? s.call(o, a, o, i) : s) || (i.exports = c)
+    }()
+}]));

File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.da.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.de.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.du.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.es.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.fi.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.fr.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.hu.js


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


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.ja.js


+ 1 - 0
docs/_build/html/_static/javascripts/lunr/lunr.jp.js

@@ -0,0 +1 @@
+module.exports=require("./lunr.ja");

+ 1 - 0
docs/_build/html/_static/javascripts/lunr/lunr.multi.js

@@ -0,0 +1 @@
+!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(o){o.multiLanguage=function(){for(var e=Array.prototype.slice.call(arguments),i=e.join("-"),t="",r=[],n=[],s=0;s<e.length;++s)"en"==e[s]?(t+="\\w",r.unshift(o.stopWordFilter),r.push(o.stemmer),n.push(o.stemmer)):(t+=o[e[s]].wordCharacters,r.unshift(o[e[s]].stopWordFilter),r.push(o[e[s]].stemmer),n.push(o[e[s]].stemmer));var p=o.trimmerSupport.generateTrimmer(t);return o.Pipeline.registerFunction(p,"lunr-multi-trimmer-"+i),r.unshift(p),function(){this.pipeline.reset(),this.pipeline.add.apply(this.pipeline,r),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add.apply(this.searchPipeline,n))}}}});

File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.nl.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.no.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.pt.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.ro.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.ru.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.stemmer.support.js


File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.sv.js


+ 1 - 0
docs/_build/html/_static/javascripts/lunr/lunr.th.js

@@ -0,0 +1 @@
+!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(t){if(void 0===t)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===t.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==t.version[0];t.th=function(){this.pipeline.reset(),this.pipeline.add(t.th.trimmer),i?this.tokenizer=t.th.tokenizer:(t.tokenizer&&(t.tokenizer=t.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=t.th.tokenizer))},t.th.wordCharacters="[฀-๿]",t.th.trimmer=t.trimmerSupport.generateTrimmer(t.th.wordCharacters),t.Pipeline.registerFunction(t.th.trimmer,"trimmer-th");var n=t.wordcut;n.init(),t.th.tokenizer=function(e){if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return i?new t.Token(e):e});var r=e.toString().replace(/^\s+/,"");return n.cut(r).split("|")}}});

File diff suppressed because it is too large
+ 0 - 0
docs/_build/html/_static/javascripts/lunr/lunr.tr.js


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