diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 033c0952c09bc81e1254724fdfcce14389465e9e..6afaeb4e0b17c0d582b7394082a5a0d486a6fa59 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,9 +2,13 @@ variables:
   GIT_SUBMODULE_STRATEGY: recursive
   GIT_SSL_CAPATH: /etc/ssl/certs/
   CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/build
-  GLUON_GIT: https://github.com/freifunk-gluon/gluon.git
-  GLUON_BRANCH: v2018.2.x
-  BROKEN: BROKEN=1
+  GLUON_GIT: https://github.com/freifunk-gluon/gluon.git/
+  GLUON_BRANCH: v2020.2.x
+  DEPRECATED: upgrade
+  # set to '' or 'BROKEN=1'
+  BROKEN: 'BROKEN=1'
+  # set to '', 'V=s' or 'V=s BUILD_LOG=1'
+  VERBOSE: ''
 
 cache:
   key: $CI_COMMIT_REF_SLUG-$GLUON_GIT-$GLUON_BRANCH
@@ -38,8 +42,12 @@ build:
   image: $CI_REGISTRY_IMAGE/build
   script:
     - export FORCE_UNSAFE_CONFIGURE=1
+      # set make options to max number of cores or -j1 if verbose is set
+    - if [ -n "$VERBOSE" ]; then MAKEOPTS="-j 1 $VERBOSE"; else MAKEOPTS="-j $(nproc --all)"; fi
+    - MAKEOPTS="$MAKEOPTS $BROKEN GLUON_DEPRECATED=$DEPRECATED"
+    - 'echo Build options: "$MAKEOPTS"'
       # former clone step
-    - ./.gitlab-ci/update-git.sh | tee -a debug.log
+    - ./.gitlab-ci/update-git.sh 2>&1 | tee -a debug.log
     - ./.gitlab-ci/build.sh -c update -b rc -a stable -n $CI_PIPELINE_ID -m "-j $(nproc --all) $BROKEN" 2>&1 | tee -a debug.log
       # We should never need this, should we?
     - ./.gitlab-ci/build.sh -c clean -b rc -a stable -n $CI_PIPELINE_ID -m "-j $(nproc --all)" 2>&1 | tee -a debug.log
@@ -57,9 +65,10 @@ build:
   artifacts:
     untracked: false
     paths:
-      - debug.log
+      - debug.*
       - output
-    expire_in: 1 day
+      - gluon/openwrt/logs/*
+    expire_in: 3 days
     when: always
   dependencies: []
 
diff --git a/.gitlab-ci/build.sh b/.gitlab-ci/build.sh
index 5a0b7aeb4114b06ff06019e09afed6b2357bd2d6..dd8eb68b958ae024aa7c1f3aa7b6c22bc25c70ea 100755
--- a/.gitlab-ci/build.sh
+++ b/.gitlab-ci/build.sh
@@ -11,8 +11,8 @@
 #   - Freifunk Fulda for the base of the gitlab-ci support
 # =====================================================================
 
-# Default make options
-MAKEOPTS="-j 4"
+# Default make options (override with -m)
+MAKEOPTS="-j 4 GLUON_DEPRECATED=upgrade"
 
 # Default is set to use current work directory
 SITEDIR="$(pwd)"
@@ -165,34 +165,35 @@ fi
 if [[ -z ${TARGETS+x} ]] ; then
   case "${BRANCH}" in
     nightly)
-      TARGETS="ar71xx-generic ar71xx-tiny x86-64 ramips-rt305x"
+      TARGETS="ar71xx-generic ar71xx-tiny x86-64 ramips-rt305x ramips-mt76x8"
       ;;
     next)
       TARGETS="ar71xx-tiny ar71xx-generic"
+      TARGETS+=" lantiq-xrx200 lantiq-xway mpc85xx-p1020" # new targets in master
       #TARGETS+=" x86-64 x86-generic" # (VMs)
       #TARGETS+=" ar71xx-nand" # (Netgear WNDR3700, WNDR4300, ZyXEL NBG6716)
       #TARGETS+=" mpc85xx-generic" # (tp-link-tl-wdr4900-v1)
-      #TARGETS+=" ramips-mt7620" # (gl-inet mt300 und mt750)
+      TARGETS+=" ramips-mt7620" # (gl-inet mt300 und mt750, TP-Link Archer C2 (v1), C20 (v1), C20i, C50 (v1))
+      TARGETS+=" ath79-generic" # (devolo WiFi pro)
       #TARGETS+=" sunxi-cortexa7" # (Banana Pi M1)
 
       # BROKEN:
       #TARGETS+=" brcm2708-bcm2708 brcm2708-bcm2709 brcm2708-bcm2710" # (raspberry Pi 1, 2 und 3)
-      #TARGETS+=" ipq40xx" # (FitzBox 4040)
-      #TARGETS+=" ramips-mt7621" # (D-Link DIR-860L (B1) Ubiquiti EdgeRouter X, ZBT WG3526)
+      TARGETS+=" ipq40xx-generic" # (FitzBox 4040)
+      TARGETS+=" ramips-mt7621" # (D-Link DIR-860L (B1) Ubiquiti EdgeRouter X, ZBT WG3526)
       #TARGETS+=" x86-geode"
       #TARGETS+=" ramips-rt305x" # BROKEN: (fonera, vocore a5)
-      #TARGETS+=" ramips-mt76x8" # BROKEN: unstable WiFi (tp-link 841 v13, Netgear R6120 und archer c50)
+      TARGETS+=" ramips-mt76x8" # (tp-link 841 v13 und archer c50)
       #TARGETS+=" ar71xx-mikrotik" # BROKEN: no sysupgrade support (mikrotik-nand)
       #TARGETS+=" brcm2708-bcm2710" # BROKEN: Untested (raspberry-pi-3)
-      #TARGETS+=" ipq806x" # BROKEN: unstable wifi drivers (tp-link-archer-c2600)
-      #TARGETS+=" mvebu-cortexa9" # BROKEN: No AP+IBSS or 11s support (linksys-wrt1200ac)
+      #TARGETS+=" ipq806x-generic" # BROKEN: unstable wifi drivers (tp-link-archer-c2600)
+      # removed: "mvebu-cortexa9" # BROKEN: No AP+IBSS or 11s support (linksys-wrt1200ac)
+      # all targets ordered:
+      # ar71xx-generic ar71xx-nand ar71xx-tiny ath79-generic brcm2708-bcm2708 brcm2708-bcm2709 ipq40xx-generic ipq806x-generic lantiq-xrx200 lantiq-xway mpc85xx-generic mpc85xx-p1020 ramips-mt7620 ramips-mt7621 ramips-mt76x8 ramips-rt305x sunxi-cortexa7 x86-64 x86-generic x86-geode"
       ;;
     *)
-      # Default to all targets
-      TARGETS="ar71xx-generic ar71xx-tiny ar71xx-nand brcm2708-bcm2708 brcm2708-bcm2709 ramips-mt7621 x86-generic x86-geode x86-64 ramips-mt7620 ramips-mt76x8 ramips-rt305x sunxi-cortexa7 ipq40xx"
-      TARGETS+=" mpc85xx-generic" # (tp-link-tl-wdr4900-v1)-
-      TARGETS+=" ipq806x" # BROKEN: unstable wifi drivers (tp-link-archer-c2600)
-      TARGETS+=" mvebu-cortexa9" # BROKEN: No AP+IBSS or 11s support (linksys-wrt1200ac)
+      # rc defaults to all targets
+      TARGETS="ar71xx-generic ar71xx-nand ar71xx-tiny brcm2708-bcm2708 brcm2708-bcm2709 ipq40xx ipq806x mpc85xx-generic mvebu-cortexa9 ramips-mt7620 ramips-mt7621 ramips-mt76x8 ramips-rt305x sunxi-cortexa7 x86-64 x86-generic x86-geode"
     ;;
   esac
 fi
@@ -223,6 +224,9 @@ if [[ -z "${RELEASE}" ]]; then
     nightly)
       RELEASE="${RELEASE}~ngly"
       ;;
+    next)
+      RELEASE="${RELEASE}~next"
+      ;;
     *)
       # Do nothing
       ;;
diff --git a/.travis.yml b/.travis.yml
index dc45fcc2eb20feb02ff6f25c3b8e37f963af2776..587cf71b04b7e08d5875af176cf63d2c0234fb8b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,6 +9,7 @@ addons:
 install:
   - eval $(luarocks path --bin)
   - luarocks install --local lua-cjson
+  - luarocks install --local luacheck
 
 script:
   - bash tests/validate_site.sh
diff --git a/README.md b/README.md
index d0eda3dc1e4f6045a315b9836d4366b841764126..cf0120db8057cbd12ed89df581cddd60d5d319c9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
 Firmware Site Config for Freifunk Kiel
 --------------------------------------
 
-The Freifunk firmware is based on gluon
+[![Build Status](https://travis-ci.org/freifunk-kiel/site-ffki.svg?branch=release-candidate)](https://travis-ci.org/freifunk-kiel/site-ffki)
+
+The Freifunk firmware is based on gluon branch v2020.2.x
 
 You can always find
 the latest gluon documentation at:
@@ -35,12 +37,19 @@ You can always find the current release at https://freifunk.in-kiel.de/firmware.
 
 # Development
 
-## Validation
+## Validation and debug
 
 You can validate your changes to this repository by calling the validate_site.sh file with
 
     tests/validate_site.sh
 
+You can debug lua scripts in modules with 
+
+    apt install luarocks
+    luarocks install --local luacheck
+    tests/validate_site.sh
+    ~/.luarocks/bin/luacheck --config "tests/.luacheckrc" /tmp/site-validate/|grep "(E"
+
 ## General process
 
 - Usually no commits should affect `release-candidate` directly.
diff --git a/make-release.sh b/make-release.sh
index 21e7d2de192eb4e96bc2ded5449c534fde1a4ee1..03fb2e1ca9f909ec259528ca9858e64eef02629b 100755
--- a/make-release.sh
+++ b/make-release.sh
@@ -15,8 +15,7 @@ set -u
 set -e
 
 # if version is unset, will use the default version from site.mk
-#VERSION=${3:-"2018.1.3~rc$(date '+%y%m%d%H%M')"}
-VERSION=${3:-"2018.2.1"}
+VERSION=${3:-"2020.2.3~rc$(date '+%y%m%d%H%M')"}
 # branch must be set to either rc, nightly or stable
 BRANCH=${2:-"stable"}
 # must point to valid ecdsa signing key created by ecdsakeygen, relative to Gluon base directory
@@ -65,19 +64,19 @@ sleep 3
 #  ramips-mt7621:  BROKEN: No AP+IBSS support, 11s has high packet loss
 #  ramips-rt305x:  BROKEN: No AP+IBSS support
 
-WRT1200AC="mvebu" # Linksys WRT1200AC BROKEN: No AP+IBSS+mesh support
+WRT1200AC="mvebu-cortexa9" # Linksys WRT1200AC BROKEN: No AP+IBSS+mesh support
 
 ONLY_11S="ramips-rt305x ramips-mt7621"    # BROKEN only
 
 ONLY_LEDE="ar71xx-tiny" # Support for for 841 on lede, needs less packages, so the 4MB will suffice!
-ONLY_LEDE+=" x86-geode ipq806x ramips-mt76x8"
+ONLY_LEDE+=" x86-geode ipq806x-generic ramips-mt76x8"
 NOT_LEDE="x86-kvm_guest" # The x86-kvm_guest target has been dropped from LEDE; x86-64 should be used
 
 BANANAPI="sunxi-cortexa7"                          # BROKEN: Untested, no sysupgrade support
 MICROTIK="ar71xx-mikrotik"                # BROKEN: no sysupgrade support
 
 RASPBPI="brcm2708-bcm2708 brcm2708-bcm2709"
-X86="x86-64 x86-generic x86-xen_domu"
+X86="x86-64 x86-generic"
 WDR4900="mpc85xx-generic"
 
 TARGETS="ar71xx-generic $ONLY_LEDE ar71xx-nand $WDR4900 $RASPBPI $X86"
@@ -92,7 +91,7 @@ fi
 for TARGET in $TARGETS; do
   date >> build.log
   echo "Starting work on target $TARGET $DEVICES" | tee -a build.log
-  OPTIONS="GLUON_TARGET=$TARGET $BROKEN $CORES GLUON_BRANCH=$BRANCH GLUON_RELEASE=$VERSION"
+  OPTIONS="GLUON_TARGET=$TARGET $BROKEN $CORES GLUON_BRANCH=$BRANCH GLUON_RELEASE=$VERSION GLUON_DEPRECATED=upgrade"
   echo -e "\n===========\n\n\n\n\nmake $OPTIONS update" >> build.log
   time make $OPTIONS update >> build.log 2>&1
   if [ $MAKE_CLEAN = 1 ]; then
diff --git a/modules b/modules
index cb0bb6310f8cc270277d23afd9c2fd370a09bbb6..bdd7f9040c05a06d8fce3baac3170da959e46ca1 100644
--- a/modules
+++ b/modules
@@ -21,12 +21,12 @@ PACKAGES_TSYS_BRANCH=master
 
 # gluon-quickfix
 PACKAGES_EULENFUNK_REPO=https://github.com/Freifunk-Nord/eulenfunk-packages
-PACKAGES_EULENFUNK_COMMIT=a4be61c88e4628d3f039510c86cd20fe7f417261
-PACKAGES_EULENFUNK_BRANCH=lede
+PACKAGES_EULENFUNK_COMMIT=58639795f6626c0031030379394d24e4aca66826
+PACKAGES_EULENFUNK_BRANCH=master
 
 # gluon-ssid-changer
 PACKAGES_SSIDCHANGER_REPO=https://github.com/Freifunk-Nord/gluon-ssid-changer
-PACKAGES_SSIDCHANGER_COMMIT=7b5b7cab11d186696b4d2319f74145b0aaeb0c3c
+PACKAGES_SSIDCHANGER_COMMIT=f6db033d6568ea27805c9694b20085b8dec5ba87
 PACKAGES_SSIDCHANGER_BRANCH=2018.1.x
 
 # ffffm-button-bind
diff --git a/patches/000-add-fonera20n-target.patch b/patches/000-add-fonera20n-target.patch
index 0493cf2767b36df76f52e0cdecd423f0e68eedd6..202c837541782de2e84fb9d6644563ccbccb7139 100644
--- a/patches/000-add-fonera20n-target.patch
+++ b/patches/000-add-fonera20n-target.patch
@@ -1,14 +1,14 @@
 diff --git a/targets/ramips-rt305x b/targets/ramips-rt305x
-index 5f1c979e..09c91e3d 100644
+index b3f8c89..a1f711f 100644
 --- a/targets/ramips-rt305x
 +++ b/targets/ramips-rt305x
-@@ -21,3 +21,9 @@ factory
- 
- device vocore-16M vocore-16M
- factory
+@@ -33,3 +33,9 @@ device('vocore-8M', 'vocore-8M', {
+ device('vocore-16M', 'vocore-16M', {
+       factory = false,
+ })
 +
-+# Fonera
++-- La Fonera
 +
-+device la-fonera-2.0n fonera20n
-+# Alternate name
-+alias fonera20n
++device('la-fonera-2.0n','fonera20n', {
++      aliases = { 'fonera20n' }
++})
diff --git a/patches/001-disable-4MB-rt305x-targets.patch b/patches/001-disable-4MB-rt305x-targets.patch
deleted file mode 100644
index 4bfd8fab2a3801c8ebc97849c3fe922c0cca727c..0000000000000000000000000000000000000000
--- a/patches/001-disable-4MB-rt305x-targets.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-diff --git a/targets/ramips-rt305x b/targets/ramips-rt305x
-index 09c91e3d..4e3b5854 100644
---- a/targets/ramips-rt305x
-+++ b/targets/ramips-rt305x
-@@ -1,17 +1,17 @@
- # A5
- 
--device a5-v11 a5-v11
-+#device a5-v11 a5-v11
- 
- 
- # D-Link
- 
--device d-link-dir-615-h1 dir-615-h1
-+#device d-link-dir-615-h1 dir-615-h1
- 
--device d-link-dir-615-d dir-615-d
--alias d-link-dir-615-d1
--alias d-link-dir-615-d2
--alias d-link-dir-615-d3
--alias d-link-dir-615-d4
-+#device d-link-dir-615-d dir-615-d
-+#alias d-link-dir-615-d1
-+#alias d-link-dir-615-d2
-+#alias d-link-dir-615-d3
-+#alias d-link-dir-615-d4
- 
- 
- # VoCore
diff --git a/patches/001-revert-respondd-do-not-join-link-local-multicast-group-on-br-client.patch b/patches/001-revert-respondd-do-not-join-link-local-multicast-group-on-br-client.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7058cf4b2f1d019e2d85b82899ecf3410da4a501
--- /dev/null
+++ b/patches/001-revert-respondd-do-not-join-link-local-multicast-group-on-br-client.patch
@@ -0,0 +1,21 @@
+commit 42cd607dd2452a9caa7a2770c8ac280c57c7b9d9
+Author: Ruben Barkow <github@r.z11.de>
+Date:   Wed Nov 6 01:47:37 2019 +0100
+
+    Revert "gluon-respondd: do not join link-local multicast group on br-client"
+    
+    This reverts commit 59a44274cb00e88e3420b4c9c4303ca70f16b211.
+
+diff --git a/package/gluon-respondd/files/etc/init.d/gluon-respondd b/package/gluon-respondd/files/etc/init.d/gluon-respondd
+index c7b071eb..ca07fa90 100755
+--- a/package/gluon-respondd/files/etc/init.d/gluon-respondd
++++ b/package/gluon-respondd/files/etc/init.d/gluon-respondd
+@@ -13,7 +13,7 @@ start_service() {
+ 	local clientdevs=$(for dev in $(echo "$ifdump" | jsonfilter -e "@.interface[@.interface='$(cat /lib/gluon/respondd/client.dev 2>/dev/null)' && @.up=true].device"); do echo " -i $dev -t $MAXDELAY";done;)
+ 
+ 	procd_open_instance
+-	procd_set_param command $DAEMON -d /usr/lib/respondd -p 1001 -g ff02::2:1001 $meshdevs -g ff05::2:1001 $clientdevs
++	procd_set_param command $DAEMON -d /usr/lib/respondd -p 1001 -g ff02::2:1001 $meshdevs $clientdevs -g ff05::2:1001 $clientdevs
+ 	procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
+ 	procd_set_param stderr 1
+ 	procd_close_instance
diff --git a/release b/release
index 05df48f632a273aa7c1c9182c818e2dc05dff158..a0ed82d6f3f69fc0c5c221a6224ee6b70985790a 100644
--- a/release
+++ b/release
@@ -1 +1 @@
-2018.2.4-BUILD
+2020.2.3-BUILD
diff --git a/site.conf b/site.conf
index 2f42b05e31bbe79c4d86aa7687dd1e9b178bdb8a..fd672f818ac970dc52ca2bade677714047201d8d 100644
--- a/site.conf
+++ b/site.conf
@@ -65,14 +65,6 @@
   wifi24 = {
     channel = 11,
     
-    -- List of supported wifi rates (optional)
-    -- without 802.11b compatibility for better performance
-    supported_rates = {6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000},
-    
-    -- List of basic wifi rates (optional, required if supported_rates is set)
-    -- without 802.11b compatibility for better performance
-    basic_rate = {6000, 9000, 18000, 36000, 54000},
-    
     -- ESSID used for client network.
     ap = {
       ssid = 'http://kiel.freifunk.net/',
@@ -143,6 +135,7 @@
       -- The default class 20 is based on the link quality (TQ) only,
       -- class 1 is calculated from both the TQ and the announced bandwidth
       gw_sel_class = 1,
+      routing_algo = 'BATMAN_IV',
     },
   },
   
diff --git a/site.mk b/site.mk
index da7f1a8f06cfaed6480cd161d8b1c858126226fe..b9ab6bf06d1954af91f6cf2c613e8682f21c86d7 100644
--- a/site.mk
+++ b/site.mk
@@ -2,8 +2,8 @@
 
 # Always call `make` from the command line with the desired release version!
 # otherwise this is generated:
-#DEFAULT_GLUON_RELEASE := 2018.2
-DEFAULT_GLUON_RELEASE := 2018.2.4~rc$(shell date '+%y%m%d')
+#DEFAULT_GLUON_RELEASE := 2020.2.3
+DEFAULT_GLUON_RELEASE := 2020.2.3~rc$(shell date '+%y%m%d')
 
 # Allow overriding the release number from the command line
 GLUON_RELEASE ?= $(DEFAULT_GLUON_RELEASE)
@@ -24,7 +24,7 @@ GLUON_WLAN_MESH ?= 11s
 
 GLUON_LANGS ?= en de
 
-# for feature packs see https://github.com/freifunk-gluon/gluon/blob/v2018.2.x/package/features
+# for feature packs see https://github.com/freifunk-gluon/gluon/blob/v2020.2.x/package/features
 GLUON_FEATURES := \
 	config-mode-geo-location-osm \
 	web-private-wifi \
@@ -32,7 +32,6 @@ GLUON_FEATURES := \
 	ebtables-filter-ra-dhcp \
 	mesh-batman-adv-15 \
 	mesh-vpn-fastd \
-	radvd \
 	radv-filterd \
 	respondd \
 	status-page \
@@ -44,7 +43,6 @@ GLUON_SITE_PACKAGES := \
 	respondd-module-airtime \
 	iwinfo \
 	iptables \
-	haveged \
 	autoupdater-proxy
 
 # from sargon:
diff --git a/tests/.luacheckrc b/tests/.luacheckrc
new file mode 100644
index 0000000000000000000000000000000000000000..890c1b6fb0519f273ba4c23d94d47885f88c27a6
--- /dev/null
+++ b/tests/.luacheckrc
@@ -0,0 +1,242 @@
+codes = true
+std = "lua51"
+self = false
+
+include_files = {
+	"*.lua",
+	"/**/files/lib/gluon/ebtables/*",
+	"/**/luasrc/**/*",
+}
+
+-- files["scripts/check_site.lua"] = {
+--	allow_defined = true,
+--	module = true,
+-- }
+
+files["/**/check_site.lua"] = {
+	globals = {
+		"alternatives",
+		"extend",
+		"in_domain",
+		"in_site",
+		"need",
+		"need_alphanumeric_key",
+		"need_array",
+		"need_array_of",
+		"need_boolean",
+		"need_chanlist",
+		"need_domain_name",
+		"need_number",
+		"need_one_of",
+		"need_string",
+		"need_string_array",
+		"need_string_array_match",
+		"need_string_match",
+		"need_table",
+		"need_value",
+		"obsolete",
+		"table_keys",
+		"this_domain",
+	},
+}
+
+files["/**/files/lib/gluon/ebtables/*"] = {
+	globals = {
+		"site",
+	},
+	new_read_globals = {
+		"chain",
+		"rule",
+	},
+	max_line_length = false,
+}
+
+files["/**/luasrc/lib/gluon/config-mode/*"] = {
+	globals = {
+		"DynamicList",
+		"Flag",
+		"Form",
+		"i18n",
+		"ListValue",
+		"renderer.render",
+		"renderer.render_string",
+		"Section",
+		"TextValue",
+		"_translate",
+		"translate",
+		"translatef",
+		"Value",
+	},
+}
+
+files["/**/luasrc/lib/gluon/**/controller/*"] = {
+	new_read_globals = {
+		"_",
+		"alias",
+		"call",
+		"entry",
+		"model",
+		"node",
+		"template",
+	},
+}
+
+files["/**/gluon-client-bridge/luasrc/usr/lib/lua/gluon/client_bridge.lua"] = {
+	globals = {
+		"next_node_macaddr",
+	},
+}
+
+files["/**/gluon-config-mode-geo-location-osm/luasrc/usr/lib/lua/gluon/config-mode/geo-location-osm.lua"] = {
+--	allow_defined = true,
+--	module = true,
+	globals = {
+		"help",
+		"MapValue",
+		"options",
+	},
+}
+
+files["/**/gluon-core/luasrc/usr/lib/lua/gluon/*"] = {
+	globals = {
+		"_M",
+	},
+}
+
+files["/**/gluon-core/luasrc/usr/lib/lua/gluon/iputil.lua"] = {
+--	allow_defined = true,
+--	module = true,
+	globals = {
+		"IPv6",
+		"mac_to_ip",
+	},
+}
+
+files["/**/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua"] = {
+--	allow_defined = true,
+--	module = true,
+	globals = {
+		"is_outdoor_device",
+		"match",
+	},
+	new_read_globals = {
+		-- globals provided by platform_info
+		"get_board_name",
+		"get_image_name",
+		"get_model",
+		"get_subtarget",
+		"get_target",
+	},
+}
+
+files["/**/gluon-core/luasrc/usr/lib/lua/gluon/users.lua"] = {
+	globals = {
+		"remove_group",
+		"remove_user",
+	},
+}
+
+files["/**/gluon-core/luasrc/usr/lib/lua/gluon/util.lua"] = {
+--	allow_defined = true,
+--	module = true,
+	globals = {
+		"add_to_set",
+		"contains",
+		"default_hostname",
+		"domain_seed_bytes",
+		"exec",
+		"find_phy",
+		"foreach_radio",
+		"generate_mac",
+		"get_mesh_devices",
+		"get_uptime",
+		"get_wlan_mac",
+		"glob",
+		"node_id",
+		"readfile",
+		"remove_from_set",
+		"replace_prefix",
+		"trim",
+	},
+}
+
+files["/**/gluon-web/luasrc/usr/lib/lua/gluon/web/*"] = {
+	globals = {
+		"Http",
+		"HTTP_MAX_CONTENT",
+		"mimedecode_message_body",
+		"parse_message_body",
+		"urldecode",
+		"urldecode_params",
+		"urlencode",
+	},
+}
+
+files["/**/gluon-web/luasrc/usr/lib/lua/gluon/web/util.lua"] = {
+	globals = {
+		"class",
+		"instanceof",
+		"pcdata",
+	},
+}
+
+files["/**/gluon-web-admin/luasrc/lib/gluon/config-mode/controller/admin/upgrade.lua"] = {
+	globals = {
+		"file",
+	},
+}
+
+files["/**/gluon-web-mesh-vpn-fastd/luasrc/lib/gluon/config-mode/model/admin/mesh_vpn_fastd.lua"] = {
+	globals = {
+		"gluon",
+	},
+}
+
+files["/**/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/datatypes.lua"] = {
+--	allow_defined = true,
+--	module = true,
+	globals = {
+		"bool",
+		"float",
+		"imax",
+		"imin",
+		"integer",
+		"ip4addr",
+		"ip6addr",
+		"ipaddr",
+		"irange",
+		"max",
+		"maxlength",
+		"min",
+		"minlength",
+		"range",
+		"ufloat",
+		"uinteger",
+		"wpakey",
+	},
+}
+
+files["/**/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model/classes.lua"] = {
+--	allow_defined = true,
+	globals = {
+		"AbstractValue",
+		"DynamicList",
+		"Flag",
+		"Form",
+		"FORM_INVALID",
+		"FORM_NODATA",
+		"FORM_VALID",
+		"ListValue",
+		"Node",
+		"Section",
+		"Template",
+		"TextValue",
+		"Value",
+	},
+}
+
+files["/**/gluon-web-osm/luasrc/usr/lib/lua/gluon/*"] = {
+	globals = {
+		"MapValue",
+	},
+}
diff --git a/tests/validate_site.sh b/tests/validate_site.sh
index ef73d84fd5659d68f2525907af7b8bc8fbe8f880..fa16723dd5f74188b1964a50fecb06e9a782aec7 100755
--- a/tests/validate_site.sh
+++ b/tests/validate_site.sh
@@ -3,9 +3,9 @@ set -uo pipefail
 
 # validate_site.sh checks if the site.conf is valid json
 GLUON_REPO="https://github.com/freifunk-gluon/gluon"
-GLUON_BRANCH='v2018.2.x'
+GLUON_BRANCH='v2020.2.x'
 GLUON_PACKAGES_REPO="https://github.com/freifunk-gluon/packages"
-GLUON_PACKAGES_BRANCH='master'
+GLUON_PACKAGES_BRANCH='v2020.2.x'
 
 P="$(pwd)"
 echo "####### check if lua5.1 is installed ..."
@@ -15,6 +15,7 @@ if [ "$?" == 1 ]; then
   echo install with sudo apt install lua5.1
   exit 1
 fi
+
 CONFIGS="site.conf"
 if [ -d "domains" ]; then
   CONFIGS="$CONFIGS "domains/*
@@ -29,13 +30,15 @@ for c in $CONFIGS; do
     echo "OK: $c"
   fi
 done
-#GLUON_SITEDIR="./" GLUON_SITE_CONFIG="" lua5.1 tests/site_config.lua
 
-echo "####### validating $P/make-release.sh ..."
-bash -n "$P/make-release.sh"
-if [ "$?" == 0 ]; then
-  echo "OK: $P/make-release.sh"
-fi
+for BASHFILE in "$P"/*.sh; do
+    [ -f "$BASHFILE" ] || continue
+  echo "####### validating $BASHFILE ..."
+  bash -n "$BASHFILE"
+  if [ "$?" == 0 ]; then
+    echo "OK: $BASHFILE"
+  fi
+done
 
 echo "####### validating $P/modules ..."
 GLUON_SITE_FEEDS="none"
@@ -44,34 +47,39 @@ testpath=/tmp/site-validate
 rm -Rf "$testpath"
 mkdir -p "$testpath/packages"
 cd "$testpath/packages"
-for feed in $GLUON_SITE_FEEDS; do
-  echo "####### checking PACKAGES_${feed^^}_REPO ..."
-  repo_var="PACKAGES_${feed^^}_REPO"
-  commit_var="PACKAGES_${feed^^}_COMMIT"
-  branch_var="PACKAGES_${feed^^}_BRANCH"
-  repo="${!repo_var}"
-  commit="${!commit_var}"
-  branch="${!branch_var}"
-  if [ "$repo" == "" ]; then
-    echo "repo $repo_var missing"
-    exit 1
-  fi
-  if [ "$commit" == "" ]; then
-    echo "commit $commit_var missing"
-    exit 1
-  fi
-	if [ "$branch" == "" ]; then
-    echo "branch $branch_var missing"
-    exit 1
-  fi
-  git clone -b "$branch" --depth 1000 --single-branch "$repo" "$feed"
-  if [ "$?" != "0" ]; then exit 1; fi
-  cd "$feed"
-  echo "git checkout $commit"
-  git checkout "$commit"
-  if [ "$?" != "0" ]; then exit 1; fi
-  cd -
-done
+if [ "$GLUON_SITE_FEEDS" != "none" ]; then
+  for feed in $GLUON_SITE_FEEDS; do
+    echo "####### checking PACKAGES_${feed^^}_REPO ..."
+    repo_var="PACKAGES_${feed^^}_REPO"
+    commit_var="PACKAGES_${feed^^}_COMMIT"
+    branch_var="PACKAGES_${feed^^}_BRANCH"
+    repo="${!repo_var}"
+    commit="${!commit_var}"
+    branch="${!branch_var}"
+    if [ "$repo" == "" ]; then
+      echo "repo $repo_var missing"
+      exit 1
+    fi
+    if [ "$commit" == "" ]; then
+      echo "commit $commit_var missing"
+      exit 1
+    fi
+  	if [ "$branch" == "" ]; then
+      echo "branch $branch_var missing"
+      exit 1
+    fi
+    git clone -b "$branch" --depth 1000 --single-branch "$repo" "$feed"
+    if [ "$?" != "0" ]; then exit 1; fi
+    cd "$feed"
+    echo "git checkout $commit"
+    git checkout "$commit"
+    if [ "$?" != "0" ]; then exit 1; fi
+    cd -
+  done
+fi
+
+echo "####### Lua linter check for all package feeds ..."
+~/.luarocks/bin/luacheck --config "$P/tests/.luacheckrc" "$testpath/packages"
 
 echo "####### downloading $GLUON_PACKAGES_REPO ..."
 git clone -b "$GLUON_PACKAGES_BRANCH" --depth 1  --single-branch "$GLUON_PACKAGES_REPO"