diff --git a/package/features b/package/features
index 72887e3af03c8d2374c3e509c1e8468c01502f0c..c6e94a1a4c0a6881d0e155d8e1420499e589c5de 100644
--- a/package/features
+++ b/package/features
@@ -16,7 +16,7 @@ when(_'web-wizard' and _'autoupdater', {
 	'gluon-config-mode-autoupdater',
 })
 
-when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
+when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger' or _'mesh-vpn-wireguard'), {
 	'gluon-config-mode-mesh-vpn',
 })
 
diff --git a/package/gluon-mesh-vpn-core/files/lib/netifd/proto/gluon_wireguard.sh b/package/gluon-mesh-vpn-core/files/lib/netifd/proto/gluon_wireguard.sh
new file mode 100755
index 0000000000000000000000000000000000000000..05eaad535682aef01ecb4b17b690212d0f29e971
--- /dev/null
+++ b/package/gluon-mesh-vpn-core/files/lib/netifd/proto/gluon_wireguard.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+PROTO_DEBUG=1
+
+. /lib/functions.sh
+. ../netifd-proto.sh
+init_proto "$@"
+
+WG=/usr/bin/wg
+
+proto_gluon_wireguard_init_config() {
+	proto_config_add_int index
+	proto_config_add_int mtu
+}
+
+interface_linklocal_from_wg_public_key() {
+	# We generate a predictable v6 address
+	local macaddr="$(printf "%s" "$1"|md5sum|sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')"
+	local oldIFS="$IFS"; IFS=':';
+	# shellcheck disable=SC2086
+	set -- $macaddr; IFS="$oldIFS"
+	echo "fe80::$1$2:$3ff:fe$4:$5$6"
+}
+
+proto_gluon_wireguard_setup() {
+	local config="$1"
+	local ifname="$2"
+
+	local index mtu
+	json_get_vars index mtu
+
+	local public_key="$(/lib/gluon/mesh-vpn/wireguard_pubkey.sh)"
+
+	# The wireguard proto itself can not be moved here, as the proto does not
+	# allow add_dynamic.
+
+	wireguard_ip=$(interface_linklocal_from_wg_public_key "$public_key")
+
+	## Add IP
+
+	proto_add_host_dependency "$config" '' "$ifname"
+	proto_init_update "$ifname" 1
+	proto_add_data
+	json_add_string zone 'wired_mesh'
+	proto_close_data
+	proto_add_ipv6_address "$wireguard_ip" "128"
+	proto_send_update "$ifname"
+
+	## wgpeerselector
+
+	json_init
+	json_add_string name "${ifname}_peerselector"
+	json_add_string ifname "$ifname"
+	json_add_string proto 'wgpeerselector'
+	json_add_string unix_group 'gluon-mesh-vpn'
+	json_add_boolean transitive 1
+	json_close_object
+	ubus call network add_dynamic "$(json_dump)"
+
+	## vxlan
+
+	json_init
+	json_add_string name "mesh-vpn"
+	json_add_string proto 'vxlan6'
+	json_add_string tunlink "$ifname"
+	# ip6addr (the lower interface ip6) is used by the vxlan.sh proto
+	json_add_string ip6addr "$wireguard_ip"
+	json_add_string peer6addr "fe80::1"
+	json_add_int vid "$(lua -e 'print(tonumber(require("gluon.util").domain_seed_bytes("gluon-mesh-vxlan", 3), 16))')"
+	json_add_boolean rxcsum '0'
+	json_add_boolean txcsum '0'
+	json_close_object
+	ubus call network add_dynamic "$(json_dump)"
+
+	proto_init_update "$ifname" 1
+	proto_send_update "$config"
+}
+
+proto_gluon_wireguard_teardown() {
+	local config="$1"
+
+	proto_init_update "*" 0
+	proto_send_update "$config"
+}
+
+add_protocol gluon_wireguard
diff --git a/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn b/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn
index 58bfa30e35263c844d7d0b888d5410b7f3fb9cbe..a476afdb3e97087c3dd6e922f5fe7cb7545ad707 100755
--- a/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn
+++ b/package/gluon-mesh-vpn-core/luasrc/lib/gluon/upgrade/500-mesh-vpn
@@ -54,6 +54,8 @@ if not uci:get('gluon', 'mesh_vpn') then
 	else
 		enabled = site.mesh_vpn.enabled(false)
 	end
+	-- wireguard is not listed here, as it didn't exist before the uci section
+	-- gluon.mesh_vpn was introduced. Therefore no migration is necessary.
 
 
 	local limit_enabled = tonumber((uci:get('simple-tc', 'mesh_vpn', 'enabled')))
diff --git a/package/gluon-mesh-vpn-wireguard/Makefile b/package/gluon-mesh-vpn-wireguard/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..c7c21e53c1b434a64f2017107a2cc05e9376d59e
--- /dev/null
+++ b/package/gluon-mesh-vpn-wireguard/Makefile
@@ -0,0 +1,13 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-mesh-vpn-wireguard
+PKG_VERSION:=1
+
+include ../gluon.mk
+
+define Package/gluon-mesh-vpn-wireguard
+  TITLE:=Support for connecting meshes via wireguard
+  DEPENDS:=+gluon-core +libgluonutil +gluon-mesh-vpn-core +wireguard-tools +@GLUON_SPECIALIZE_KERNEL:KERNEL_TUN +wgpeerselector +libubus
+endef
+
+$(eval $(call BuildPackageGluon,gluon-mesh-vpn-wireguard))
diff --git a/package/gluon-mesh-vpn-wireguard/check_site.lua b/package/gluon-mesh-vpn-wireguard/check_site.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f5a0d94e57d13864d6df00b7f5e8ee526dfb8eb1
--- /dev/null
+++ b/package/gluon-mesh-vpn-wireguard/check_site.lua
@@ -0,0 +1,9 @@
+local function check_peer(k)
+	need_alphanumeric_key(k)
+
+	need_string_match(in_domain(extend(k,
+		{'public_key'})), "^" .. ("[%a%d+/]"):rep(42) .. "[AEIMQUYcgkosw480]=$")
+	need_string(in_domain(extend(k, {'endpoint'})))
+end
+
+need_table({'mesh_vpn', 'wireguard', 'peers'}, check_peer)
diff --git a/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/provider/wireguard b/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/provider/wireguard
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/wireguard_pubkey.sh b/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/wireguard_pubkey.sh
new file mode 100755
index 0000000000000000000000000000000000000000..bbcba5717a6198a72afb6b6802c0c644c114ae2f
--- /dev/null
+++ b/package/gluon-mesh-vpn-wireguard/files/lib/gluon/mesh-vpn/wireguard_pubkey.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+INCLUDE_ONLY=1
+. /lib/netifd/proto/wireguard.sh
+
+ensure_key_is_generated wg_mesh
+uci get "network.wg_mesh.private_key" | /usr/bin/wg pubkey
diff --git a/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard b/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard
new file mode 100755
index 0000000000000000000000000000000000000000..dfadc95bd5b0b45245ec3da3287f5ec4edc078e4
--- /dev/null
+++ b/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard
@@ -0,0 +1,39 @@
+#!/usr/bin/lua
+
+local uci = require('simple-uci').cursor()
+local site = require 'gluon.site'
+
+local private_key = uci:get("network", 'wg_mesh', "private_key")
+
+if not private_key or not private_key:match("^" .. ("[%a%d+/]"):rep(42) .. "[AEIMQUYcgkosw480]=$") then
+  private_key = "generate"
+end
+
+uci:section('network', 'interface', 'wg_mesh', {
+  proto = 'wireguard',
+  fwmark = 1,
+  private_key = private_key,
+})
+
+uci:section('network', 'interface', 'mesh_wg_mesh', {
+	ifname = 'wg_mesh',
+	proto = 'gluon_wireguard'
+})
+
+-- Clean up previous configuration
+uci:delete_all('wgpeerselector', 'peer', function(peer)
+  return peer.preserve ~= '1'
+end)
+
+for name, peer in pairs(site.mesh_vpn.wireguard.peers()) do
+  uci:section("wgpeerselector", "peer", name, {
+    enabled = true,
+    endpoint = peer.endpoint,
+    public_key = peer.public_key,
+    allowed_ips = { "fe80::1/128" },
+    ifname = 'wg_mesh',
+  })
+end
+
+uci:save("wgpeerselector")
+uci:save("network")
diff --git a/package/gluon-mesh-vpn-wireguard/luasrc/usr/lib/lua/gluon/mesh-vpn/provider/wireguard.lua b/package/gluon-mesh-vpn-wireguard/luasrc/usr/lib/lua/gluon/mesh-vpn/provider/wireguard.lua
new file mode 100644
index 0000000000000000000000000000000000000000..5065e217ce1ae1964f16a38344f29c19b1e7c542
--- /dev/null
+++ b/package/gluon-mesh-vpn-wireguard/luasrc/usr/lib/lua/gluon/mesh-vpn/provider/wireguard.lua
@@ -0,0 +1,37 @@
+local uci = require('simple-uci').cursor()
+
+local site = require 'gluon.site'
+local util = require 'gluon.util'
+local vpn_core = require 'gluon.mesh-vpn'
+
+local M = {}
+
+function M.public_key()
+	return util.trim(util.exec("/lib/gluon/mesh-vpn/wireguard_pubkey.sh"))
+end
+
+function M.enable(val)
+	uci:set('network', 'wg_mesh', 'disabled', not val)
+	uci:save('network')
+end
+
+function M.active()
+	return site.mesh_vpn.wireguard() ~= nil
+end
+
+function M.set_limit(ingress_limit, egress_limit)
+	-- TODO: Test that limiting this via simple-tc here is correct!
+	uci:delete('simple-tc', 'mesh_vpn')
+	if ingress_limit ~= nil and egress_limit ~= nil then
+		uci:section('simple-tc', 'interface', 'mesh_vpn', {
+			ifname = vpn_core.get_interface(),
+			enabled = true,
+			limit_egress = egress_limit,
+			limit_ingress = ingress_limit,
+		})
+	end
+
+	uci:save('simple-tc')
+end
+
+return M
diff --git a/package/gluon-mesh-vpn-wireguard/src/Makefile b/package/gluon-mesh-vpn-wireguard/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0b027848f69fab204047104bca849f1b07430738
--- /dev/null
+++ b/package/gluon-mesh-vpn-wireguard/src/Makefile
@@ -0,0 +1,6 @@
+all: respondd.so
+
+CFLAGS += -Wall -Werror-implicit-function-declaration
+
+respondd.so: respondd.c
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -D_GNU_SOURCE -o $@ $^ $(LDLIBS) -lgluonutil -lubus
diff --git a/package/gluon-mesh-vpn-wireguard/src/respondd.c b/package/gluon-mesh-vpn-wireguard/src/respondd.c
new file mode 100644
index 0000000000000000000000000000000000000000..59f6511c1f7a6be72929c94ae5f17d3d146cc3b6
--- /dev/null
+++ b/package/gluon-mesh-vpn-wireguard/src/respondd.c
@@ -0,0 +1,231 @@
+/*
+  Copyright (c) 2020, Leonardo Mörlein <me@irrelefant.net>
+  Copyright (c) 2016, Matthias Schiffer <mschiffer@universe-factory.net>
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <respondd.h>
+
+#include <json-c/json.h>
+#include <libgluonutil.h>
+#include <uci.h>
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "libubus.h"
+
+static struct json_object * stdout_read(const char *cmd, const char *skip, bool oneword) {
+	FILE *f = popen(cmd, "r");
+	if (!f)
+		return NULL;
+
+	char *line = NULL;
+	size_t len = 0;
+	size_t skiplen = strlen(skip);
+
+	ssize_t read_chars = getline(&line, &len, f);
+
+	pclose(f);
+
+	if (read_chars < 1) {
+		free(line);
+		return NULL;
+	}
+
+	if (line[read_chars-1] == '\n')
+		line[read_chars-1] = '\0';
+
+	const char *content = line;
+	if (strncmp(content, skip, skiplen) == 0)
+		content += skiplen;
+
+	if (oneword) {
+		for (int i = 0; i < len; i++) {
+			if (isspace(line[i])) {
+				 line[i] = 0;
+			}
+		}
+	}
+
+	struct json_object *ret = gluonutil_wrap_string(content);
+	free(line);
+	return ret;
+}
+
+static struct json_object * get_wireguard_public_key(void) {
+	return stdout_read("exec /lib/gluon/mesh-vpn/wireguard_pubkey.sh", "", false);
+}
+
+static struct json_object * get_wireguard_version(void) {
+	return stdout_read("exec wg -v", "wireguard-tools ", true);
+}
+
+static bool wireguard_enabled(void) {
+	bool enabled = true;
+
+	struct uci_context *ctx = uci_alloc_context();
+	if (!ctx)
+		goto disabled_nofree;
+	ctx->flags &= ~UCI_FLAG_STRICT;
+
+	struct uci_package *p;
+	if (uci_load(ctx, "network", &p))
+		goto disabled;
+
+	struct uci_section *s = uci_lookup_section(ctx, p, "wg_mesh");
+	if (!s)
+		goto disabled;
+
+	const char *disabled_str = uci_lookup_option_string(ctx, s, "disabled");
+	if (!disabled_str || !strcmp(disabled_str, "1"))
+		enabled = false;
+
+disabled:
+	uci_free_context(ctx);
+
+disabled_nofree:
+	return enabled;
+}
+
+static bool get_pubkey_privacy(void) {
+	bool ret = true;
+	struct json_object *site = NULL;
+
+	site = gluonutil_load_site_config();
+	if (!site)
+		goto end;
+
+	struct json_object *mesh_vpn;
+	if (!json_object_object_get_ex(site, "mesh_vpn", &mesh_vpn))
+		goto end;
+
+	struct json_object *pubkey_privacy;
+	if (!json_object_object_get_ex(mesh_vpn, "pubkey_privacy", &pubkey_privacy))
+		goto end;
+
+	ret = json_object_get_boolean(pubkey_privacy);
+
+end:
+	json_object_put(site);
+
+	return ret;
+}
+
+static struct json_object * get_wireguard(void) {
+	bool wg_enabled = wireguard_enabled();
+
+	struct json_object *ret = json_object_new_object();
+	json_object_object_add(ret, "version", get_wireguard_version());
+	json_object_object_add(ret, "enabled", json_object_new_boolean(wg_enabled));
+	if (wg_enabled && !get_pubkey_privacy())
+		json_object_object_add(ret, "public_key", get_wireguard_public_key());
+	return ret;
+}
+
+static struct json_object * respondd_provider_nodeinfo(void) {
+	struct json_object *ret = json_object_new_object();
+
+	struct json_object *software = json_object_new_object();
+	json_object_object_add(software, "wireguard", get_wireguard());
+	json_object_object_add(ret, "software", software);
+
+	return ret;
+}
+
+static json_object *blobmsg_attr2json(struct blob_attr *attr, int type)
+{
+	int len = blobmsg_data_len(attr);
+	struct blobmsg_data *data = blobmsg_data(attr);
+	struct blob_attr *inner_attr;
+	json_object *res = NULL;
+	switch(type) {
+		case BLOBMSG_TYPE_STRING:
+			return gluonutil_wrap_string(blobmsg_get_string(attr));
+		case BLOBMSG_TYPE_BOOL:
+			return json_object_new_boolean(blobmsg_get_bool(attr));
+		case BLOBMSG_TYPE_INT16:
+			return json_object_new_double(blobmsg_get_u16(attr));
+		case BLOBMSG_TYPE_INT32:
+			return json_object_new_double(blobmsg_get_u32(attr));
+		case BLOBMSG_TYPE_INT64:
+			return json_object_new_double(blobmsg_get_u64(attr));
+		case BLOBMSG_TYPE_DOUBLE:
+			return json_object_new_double(blobmsg_get_double(attr));
+		case BLOBMSG_TYPE_TABLE:
+			res = json_object_new_object();
+			__blob_for_each_attr(inner_attr, data, len) {
+				json_object_object_add(res, blobmsg_name(inner_attr), blobmsg_attr2json(inner_attr, blobmsg_type(inner_attr)));
+			};
+			break;
+		case BLOBMSG_TYPE_ARRAY:
+			res = json_object_new_array();
+			__blob_for_each_attr(inner_attr, data, len) {
+				json_object_array_add(res, blobmsg_attr2json(inner_attr, blobmsg_type(inner_attr)));
+			}
+			break;
+	}
+
+	return res;
+}
+
+static void cb_wgpeerselector_vpn(struct ubus_request *req, int type, struct blob_attr *msg)
+{
+	json_object_object_add(req->priv, "mesh_vpn", blobmsg_attr2json(msg, type));
+}
+
+static struct json_object * respondd_provider_statistics(void) {
+	struct json_object *ret = json_object_new_object();
+	struct ubus_context *ctx = ubus_connect(NULL);
+	uint32_t ubus_path_id;
+
+	if (!ctx) {
+		fprintf(stderr, "Error in gluon-mesh-vpn-wireguard.so: Failed to connect to ubus.\n");
+		goto err;
+	}
+
+	if (ubus_lookup_id(ctx, "wgpeerselector.wg_mesh", &ubus_path_id)) {
+		goto err;
+	}
+
+	ubus_invoke(ctx, ubus_path_id, "status", NULL, cb_wgpeerselector_vpn, ret, 1000);
+
+err:
+	if (ctx)
+		ubus_free(ctx);
+	return ret;
+}
+
+
+const struct respondd_provider_info respondd_providers[] = {
+	{"nodeinfo", respondd_provider_nodeinfo},
+	{"statistics", respondd_provider_statistics},
+	{}
+};
diff --git a/patches/openwrt/0005-package-uci-backport-cli-add-option-for-changing-save-path.patch b/patches/openwrt/0005-package-uci-backport-cli-add-option-for-changing-save-path.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a3f4f92abfffe92a291c879e184e580291380f91
--- /dev/null
+++ b/patches/openwrt/0005-package-uci-backport-cli-add-option-for-changing-save-path.patch
@@ -0,0 +1,83 @@
+From: Leonardo Mörlein <me@irrelefant.net>
+Date: Sat, 16 Jan 2021 23:11:01 +0100
+Subject: package/uci: backport: "cli: add option for changing save path"
+
+This is a backport of
+
+https://git.openwrt.org/?p=project/uci.git;a=commit;h=4b3db1179747b6a6779029407984bacef851325c
+
+diff --git a/package/system/uci/Makefile b/package/system/uci/Makefile
+index 75fc1bdfad0694aac99830b9b0cc87b42ea16e7d..924d5bb4824f567888e2ffd2954429af8f4fd504 100644
+--- a/package/system/uci/Makefile
++++ b/package/system/uci/Makefile
+@@ -9,7 +9,7 @@
+ include $(TOPDIR)/rules.mk
+ 
+ PKG_NAME:=uci
+-PKG_RELEASE:=5
++PKG_RELEASE:=6
+ 
+ PKG_SOURCE_URL=$(PROJECT_GIT)/project/uci.git
+ PKG_SOURCE_PROTO:=git
+diff --git a/package/system/uci/patches/0001-cli-add-option-for-changin-save-path.patch b/package/system/uci/patches/0001-cli-add-option-for-changin-save-path.patch
+new file mode 100644
+index 0000000000000000000000000000000000000000..377aec41fe6928aa26bccdde9fd77576d57ec4ed
+--- /dev/null
++++ b/package/system/uci/patches/0001-cli-add-option-for-changin-save-path.patch
+@@ -0,0 +1,56 @@
++From: Rafał Miłecki <rafal@milecki.pl>
++Date: Mon, 12 Apr 2021 14:05:52 +0000 (+0200)
++Subject: cli: add option for changing save path
++X-Git-Url: http://git.openwrt.org/?p=project%2Fuci.git;a=commitdiff_plain;h=4b3db1179747b6a6779029407984bacef851325c;hp=52bbc99f69ea6f67b6fe264f424dac91bde5016c
++
++cli: add option for changing save path
++
++Save path is a directory where config change (delta) files are stored.
++Having a custom individual save dir can be used to prevent two (or more)
++"uci" cli callers (e.g. bash scripts) from commiting each other changes.
++
++In the following example:
++
++App0					App1
++----					----
++uci set system.@system[0].timezone=UTC
++					uci set system.@system[0].hostname=OpenWrt
++					uci commit system
++
++App1 would unintentionally commit changes made by App0. This can be
++avoided by at least 1 "uci" cli user specifying a custom -t option.
++
++Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
++---
++
++diff --git a/cli.c b/cli.c
++index 267437d..2fce39d 100644
++--- a/cli.c
+++++ b/cli.c
++@@ -167,6 +167,7 @@ static void uci_usage(void)
++ 		"\t-N         don't name unnamed sections\n"
++ 		"\t-p <path>  add a search path for config change files\n"
++ 		"\t-P <path>  add a search path for config change files and use as default\n"
+++		"\t-t <path>  set save path for config change files\n"
++ 		"\t-q         quiet mode (don't print error messages)\n"
++ 		"\t-s         force strict mode (stop on parser errors, default)\n"
++ 		"\t-S         disable strict mode\n"
++@@ -706,7 +707,7 @@ int main(int argc, char **argv)
++ 		return 1;
++ 	}
++ 
++-	while((c = getopt(argc, argv, "c:d:f:LmnNp:P:sSqX")) != -1) {
+++	while((c = getopt(argc, argv, "c:d:f:LmnNp:P:qsSt:X")) != -1) {
++ 		switch(c) {
++ 			case 'c':
++ 				uci_set_confdir(ctx, optarg);
++@@ -754,6 +755,9 @@ int main(int argc, char **argv)
++ 			case 'q':
++ 				flags |= CLI_FLAG_QUIET;
++ 				break;
+++			case 't':
+++				uci_set_savedir(ctx, optarg);
+++				break;
++ 			case 'X':
++ 				flags &= ~CLI_FLAG_SHOW_EXT;
++ 				break;
diff --git a/patches/openwrt/0006-wireguard-tools-allow-generating-private_key-v3.patch b/patches/openwrt/0006-wireguard-tools-allow-generating-private_key-v3.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9d801d023cdf76e8da8e796d5937c5da73683e55
--- /dev/null
+++ b/patches/openwrt/0006-wireguard-tools-allow-generating-private_key-v3.patch
@@ -0,0 +1,53 @@
+From: lemoer <git@irrelefant.net>
+Date: Sat, 3 Jul 2021 22:50:29 +0200
+Subject: wireguard-tools: allow generating private_key (v3)
+
+When the uci configuration is created automatically during a very early
+stage, where no entropy daemon is set up, generating the key directly is
+not an option. Therefore we allow to set the private_key to "generate"
+and generate the private key directly before the interface is taken up.
+
+v3: Somebody has implemented another uci cli flag '-t' upstream to handle
+    this, before my patch to implement the new uci flag '-x' syntax was
+    accepted. So I dropped my suggestion of '-x'.
+v2: We now use a new uci cli flag to commit only the private_key and do
+    not commit uncommited user changes. This is not yet upstream as of
+    now.
+
+diff --git a/package/network/utils/wireguard-tools/files/wireguard.sh b/package/network/utils/wireguard-tools/files/wireguard.sh
+index 63261aea71daa058bf37014ba7d670a5e74a2e04..845f9eb902bf3655b631d52aa3ee69231366f657 100644
+--- a/package/network/utils/wireguard-tools/files/wireguard.sh
++++ b/package/network/utils/wireguard-tools/files/wireguard.sh
+@@ -95,6 +95,23 @@ proto_wireguard_setup_peer() {
+ 	fi
+ }
+ 
++ensure_key_is_generated() {
++	local private_key
++	private_key="$(uci get network."$1".private_key)"
++
++	if [ "$private_key" == "generate" ]; then
++		local ucitmp
++		oldmask="$(umask)"
++		umask 077
++		ucitmp="$(mktemp -d)"
++		private_key="$("${WG}" genkey)"
++		uci -q -t "$ucitmp" set network."$1".private_key="$private_key" && \
++			uci -q -t "$ucitmp" commit network
++		rm -rf "$ucitmp"
++		umask "$oldmask"
++	fi
++}
++
+ proto_wireguard_setup() {
+ 	local config="$1"
+ 	local wg_dir="/tmp/wireguard"
+@@ -104,6 +121,8 @@ proto_wireguard_setup() {
+ 	local listen_port
+ 	local mtu
+ 
++	ensure_key_is_generated "${config}"
++
+ 	config_load network
+ 	config_get private_key "${config}" "private_key"
+ 	config_get listen_port "${config}" "listen_port"