diff --git a/Makefile b/Makefile
index a98491041d8f54111750af2661c58bfbdf9824c0..34343356c3e47584f873dfb67ab30ad39ef2204f 100644
--- a/Makefile
+++ b/Makefile
@@ -103,6 +103,13 @@ endef
 list-targets: FORCE
 	@$(foreach target,$(GLUON_TARGETS),echo '$(target)';)
 
+lint: lint-lua lint-sh
+
+lint-lua: FORCE
+	@scripts/lint-lua.sh
+
+lint-sh: FORCE
+	@scripts/lint-sh.sh
 
 GLUON_DEFAULT_PACKAGES := hostapd-mini
 
diff --git a/scripts/lint-lua.sh b/scripts/lint-lua.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e958e3f16042870c73ea322e16f549b89c242f70
--- /dev/null
+++ b/scripts/lint-lua.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -e
+
+luacheck package scripts targets
diff --git a/scripts/lint-sh.sh b/scripts/lint-sh.sh
new file mode 100755
index 0000000000000000000000000000000000000000..71ce54633c282f205359f360a7c002328b25d2c1
--- /dev/null
+++ b/scripts/lint-sh.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+set -e
+
+is_scriptfile() {
+	echo "$1" | grep -qE '.*\.sh$' || head -n1 "$1" | grep -qE '^#.*(sh|bash)$'
+}
+
+find contrib -type f | while read -r file; do
+	is_scriptfile "$file" || continue
+
+	echo "Checking $file"
+	shellcheck -f gcc "$file"
+done
+
+find package -type f | while read -r file; do
+	is_scriptfile "$file" || continue
+
+	echo "Checking $file"
+	shellcheck -f gcc -x -s sh -e SC2039,SC1091,SC2155,SC2034 "$file"
+done
+
+find scripts -type f | while read -r file; do
+	is_scriptfile "$file" || continue
+
+	echo "Checking $file"
+	shellcheck -f gcc -x -e SC2154,SC1090,SC2181,SC2155,SC2148,SC2034,SC2148 "$file"
+done