Przeglądaj źródła

Merge gluon packages

The gluon packages will be maintained in the gluon main repository in the
future.
Matthias Schiffer 9 lat temu
rodzic
commit
216c9ce350
100 zmienionych plików z 2487 dodań i 0 usunięć
  1. 32 0
      package/gluon-alfred/Makefile
  2. 1 0
      package/gluon-alfred/files/lib/gluon/cron/alfred
  3. 19 0
      package/gluon-alfred/files/lib/gluon/upgrade/500-enable-alfred
  4. 32 0
      package/gluon-announce/Makefile
  5. 10 0
      package/gluon-announce/files/lib/gluon/announce/collect.lua
  6. 1 0
      package/gluon-announce/files/lib/gluon/announce/neighbours.d/node_id
  7. 1 0
      package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hardware/model
  8. 1 0
      package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hostname
  9. 1 0
      package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/network/mac
  10. 1 0
      package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/node_id
  11. 4 0
      package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/software/firmware
  12. 3 0
      package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/system/site_code
  13. 1 0
      package/gluon-announce/files/lib/gluon/announce/statistics.d/idletime
  14. 1 0
      package/gluon-announce/files/lib/gluon/announce/statistics.d/loadavg
  15. 13 0
      package/gluon-announce/files/lib/gluon/announce/statistics.d/memory
  16. 1 0
      package/gluon-announce/files/lib/gluon/announce/statistics.d/node_id
  17. 3 0
      package/gluon-announce/files/lib/gluon/announce/statistics.d/processes
  18. 4 0
      package/gluon-announce/files/lib/gluon/announce/statistics.d/rootfs_usage
  19. 1 0
      package/gluon-announce/files/lib/gluon/announce/statistics.d/uptime
  20. 33 0
      package/gluon-announce/files/usr/lib/lua/gluon/announce.lua
  21. 40 0
      package/gluon-announced/Makefile
  22. 45 0
      package/gluon-announced/files/etc/hotplug.d/iface/10-gluon-announced
  23. 6 0
      package/gluon-announced/src/Makefile
  24. 221 0
      package/gluon-announced/src/gluon-announced.c
  25. 36 0
      package/gluon-authorized-keys/Makefile
  26. 1 0
      package/gluon-authorized-keys/check_site.lua
  27. 22 0
      package/gluon-authorized-keys/files/lib/gluon/upgrade/100-authorized-keys
  28. 42 0
      package/gluon-autoupdater/Makefile
  29. 12 0
      package/gluon-autoupdater/check_site.lua
  30. 7 0
      package/gluon-autoupdater/files/lib/gluon/announce/nodeinfo.d/software/autoupdater
  31. 57 0
      package/gluon-autoupdater/files/lib/gluon/upgrade/500-autoupdater
  32. 40 0
      package/gluon-config-mode-autoupdater/Makefile
  33. 19 0
      package/gluon-config-mode-autoupdater/files/lib/gluon/config-mode/wizard/0050-autoupdater-info.lua
  34. 17 0
      package/gluon-config-mode-autoupdater/i18n/de.po
  35. 7 0
      package/gluon-config-mode-autoupdater/i18n/gluon-config-mode-autoupdater.pot
  36. 36 0
      package/gluon-config-mode-contact-info/Makefile
  37. 34 0
      package/gluon-config-mode-contact-info/files/lib/gluon/config-mode/wizard/0500-contact-info.lua
  38. 27 0
      package/gluon-config-mode-contact-info/i18n/de.po
  39. 14 0
      package/gluon-config-mode-contact-info/i18n/gluon-config-mode-contact-info.pot
  40. 39 0
      package/gluon-config-mode-core/Makefile
  41. 3 0
      package/gluon-config-mode-core/files/lib/gluon/config-mode/reboot/0900-msg-reboot.lua
  42. 89 0
      package/gluon-config-mode-core/files/usr/lib/lua/luci/controller/gluon-config-mode/index.lua
  43. 38 0
      package/gluon-config-mode-core/files/usr/lib/lua/luci/model/cbi/gluon-config-mode/wizard.lua
  44. 46 0
      package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/cbi/wizard.htm
  45. 17 0
      package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/reboot.htm
  46. 24 0
      package/gluon-config-mode-core/i18n/de.po
  47. 14 0
      package/gluon-config-mode-core/i18n/gluon-config-mode-core.pot
  48. 36 0
      package/gluon-config-mode-geo-location/Makefile
  49. 60 0
      package/gluon-config-mode-geo-location/files/lib/gluon/config-mode/wizard/0400-geo-location.lua
  50. 36 0
      package/gluon-config-mode-geo-location/i18n/de.po
  51. 20 0
      package/gluon-config-mode-geo-location/i18n/gluon-config-mode-geo-location.pot
  52. 36 0
      package/gluon-config-mode-hostname/Makefile
  53. 21 0
      package/gluon-config-mode-hostname/files/lib/gluon/config-mode/wizard/0100-hostname.lua
  54. 14 0
      package/gluon-config-mode-hostname/i18n/de.po
  55. 5 0
      package/gluon-config-mode-hostname/i18n/gluon-config-mode-hostname.pot
  56. 36 0
      package/gluon-config-mode-mesh-vpn/Makefile
  57. 29 0
      package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/reboot/0100-mesh-vpn.lua
  58. 64 0
      package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua
  59. 36 0
      package/gluon-config-mode-mesh-vpn/i18n/de.po
  60. 22 0
      package/gluon-config-mode-mesh-vpn/i18n/gluon-config-mode-mesh-vpn.pot
  61. 59 0
      package/gluon-core/Makefile
  62. 10 0
      package/gluon-core/check_site.lua
  63. 5 0
      package/gluon-core/files/etc/uci-defaults/zzz-gluon-upgrade
  64. 0 0
      package/gluon-core/files/lib/gluon/core/sysconfig/.keep
  65. 10 0
      package/gluon-core/files/lib/gluon/upgrade/001-upgrade
  66. 42 0
      package/gluon-core/files/lib/gluon/upgrade/010-primary-mac
  67. 36 0
      package/gluon-core/files/lib/gluon/upgrade/020-interfaces
  68. 18 0
      package/gluon-core/files/lib/gluon/upgrade/030-system
  69. 5 0
      package/gluon-core/files/lib/gluon/upgrade/100-dnsmasq
  70. 58 0
      package/gluon-core/files/lib/gluon/upgrade/110-network
  71. 14 0
      package/gluon-core/files/lib/gluon/upgrade/120-ntp-servers
  72. 5 0
      package/gluon-core/files/lib/gluon/upgrade/130-reboot-on-oom
  73. 30 0
      package/gluon-core/files/lib/gluon/upgrade/140-firewall-rules
  74. 12 0
      package/gluon-core/files/lib/gluon/upgrade/200-wireless
  75. 11 0
      package/gluon-core/files/lib/gluon/upgrade/999-version
  76. 1 0
      package/gluon-core/files/lib/upgrade/keep.d/gluon
  77. 31 0
      package/gluon-core/files/usr/lib/lua/gluon/platform.lua
  78. 21 0
      package/gluon-core/files/usr/lib/lua/gluon/site_config.lua
  79. 34 0
      package/gluon-core/files/usr/lib/lua/gluon/sysconfig.lua
  80. 8 0
      package/gluon-core/files/usr/lib/lua/gluon/sysctl.lua
  81. 33 0
      package/gluon-core/files/usr/lib/lua/gluon/users.lua
  82. 79 0
      package/gluon-core/files/usr/lib/lua/gluon/util.lua
  83. 40 0
      package/gluon-cron/Makefile
  84. 18 0
      package/gluon-cron/files/etc/init.d/gluon-cron
  85. 0 0
      package/gluon-cron/files/lib/gluon/cron/.keep
  86. 3 0
      package/gluon-cron/src/Makefile
  87. 316 0
      package/gluon-cron/src/gluon-crond.c
  88. 40 0
      package/gluon-ebtables-filter-multicast/Makefile
  89. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain
  90. 3 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-arp
  91. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-babel
  92. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-btlpd
  93. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv4
  94. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv6
  95. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmp
  96. 2 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmpv6
  97. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-igmp
  98. 2 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ospf
  99. 1 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ripng
  100. 2 0
      package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/300-mcast

+ 32 - 0
package/gluon-alfred/Makefile

@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-alfred
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-alfred
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-core +gluon-announce +gluon-cron +alfred
+  TITLE:=Configure alfred
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-alfred/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-alfred))

+ 1 - 0
package/gluon-alfred/files/lib/gluon/cron/alfred

@@ -0,0 +1 @@
+* * * * * /lib/gluon/announce/collect.lua nodeinfo | gzip | alfred -s 158; /lib/gluon/announce/collect.lua statistics | gzip | alfred -s 159; /lib/gluon/announce/collect.lua neighbours | gzip | alfred -s 160

+ 19 - 0
package/gluon-alfred/files/lib/gluon/upgrade/500-enable-alfred

@@ -0,0 +1,19 @@
+#!/usr/bin/lua
+
+local uci = require 'luci.model.uci'
+local c = uci.cursor()
+
+
+c:delete('alfred', 'alfred')
+c:section('alfred', 'alfred', 'alfred',
+	  {
+		  interface = 'br-client',
+		  mode = 'slave',
+		  batmanif = 'bat0',
+		  start_vis = '1',
+		  run_facters = '0',
+	  }
+)
+
+c:save('alfred')
+c:commit('alfred')

+ 32 - 0
package/gluon-announce/Makefile

@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-announce
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-announce
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-core +luci-lib-json +lua-ethtool-stats
+  TITLE:=Lua scripts announcing various information
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-announce/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-announce))

+ 10 - 0
package/gluon-announce/files/lib/gluon/announce/collect.lua

@@ -0,0 +1,10 @@
+#!/usr/bin/lua
+
+local announce = require 'gluon.announce'
+local json = require 'luci.json'
+local ltn12 = require 'luci.ltn12'
+
+local announce_dir = '/lib/gluon/announce/' .. arg[1] .. '.d'
+
+encoder = json.Encoder(announce.collect_dir(announce_dir))
+ltn12.pump.all(encoder:source(), ltn12.sink.file(io.stdout))

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/neighbours.d/node_id

@@ -0,0 +1 @@
+return require('gluon.util').node_id()

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hardware/model

@@ -0,0 +1 @@
+return require('platform_info').get_model()

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/hostname

@@ -0,0 +1 @@
+return uci:get_first('system', 'system', 'hostname')

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/network/mac

@@ -0,0 +1 @@
+return require('gluon.sysconfig').primary_mac

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/node_id

@@ -0,0 +1 @@
+return require('gluon.util').node_id()

+ 4 - 0
package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/software/firmware

@@ -0,0 +1,4 @@
+return {
+	base = 'gluon-' .. util.trim(fs.readfile('/lib/gluon/gluon-version')),
+	release = util.trim(fs.readfile('/lib/gluon/release')),
+}

+ 3 - 0
package/gluon-announce/files/lib/gluon/announce/nodeinfo.d/system/site_code

@@ -0,0 +1,3 @@
+local site = require 'gluon.site_config'
+
+return site.site_code

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/statistics.d/idletime

@@ -0,0 +1 @@
+return tonumber(fs.readfile('/proc/uptime'):match('^[^ ]+ ([^ ]+)'))

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/statistics.d/loadavg

@@ -0,0 +1 @@
+return tonumber(fs.readfile('/proc/loadavg'):match('^([^ ]+) '))

+ 13 - 0
package/gluon-announce/files/lib/gluon/announce/statistics.d/memory

@@ -0,0 +1,13 @@
+local data = fs.readfile('/proc/meminfo')
+
+local fields = {}
+for k, v in data:gmatch('([^\n:]+):%s*(%d+) kB') do
+	fields[k] = tonumber(v)
+end
+
+return {
+	total = fields.MemTotal,
+	free = fields.MemFree,
+	buffers = fields.Buffers,
+	cached = fields.Cached,
+}

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/statistics.d/node_id

@@ -0,0 +1 @@
+return require('gluon.util').node_id()

+ 3 - 0
package/gluon-announce/files/lib/gluon/announce/statistics.d/processes

@@ -0,0 +1,3 @@
+local running, total = fs.readfile('/proc/loadavg'):match('^[^ ]+ [^ ]+ [^ ]+ (%d+)/(%d+)')
+
+return { running = tonumber(running), total = tonumber(total) }

+ 4 - 0
package/gluon-announce/files/lib/gluon/announce/statistics.d/rootfs_usage

@@ -0,0 +1,4 @@
+local fs = require "nixio.fs"
+
+local st = fs.statvfs("/")
+return 1 - st.bfree / st.blocks

+ 1 - 0
package/gluon-announce/files/lib/gluon/announce/statistics.d/uptime

@@ -0,0 +1 @@
+return tonumber(fs.readfile('/proc/uptime'):match('^([^ ]+) '))

+ 33 - 0
package/gluon-announce/files/usr/lib/lua/gluon/announce.lua

@@ -0,0 +1,33 @@
+#!/usr/bin/lua
+
+module('gluon.announce', package.seeall)
+
+fs = require 'luci.fs'
+uci = require('luci.model.uci').cursor()
+util = require 'luci.util'
+
+local function collect_entry(entry)
+	if fs.isdirectory(entry) then
+		return collect_dir(entry)
+	else
+		return setfenv(loadfile(entry), _M)()
+	end
+end
+
+function collect_dir(dir)
+	local ret = {}
+
+	for _, entry in ipairs(fs.dir(dir)) do
+		if entry:sub(1, 1) ~= '.' then
+			local ok, val = pcall(collect_entry, dir .. '/' .. entry)
+			if ok then
+				ret[entry] = val
+			else
+				io.stderr:write(val, '\n')
+			end
+		end
+	end
+
+	return ret
+end
+

+ 40 - 0
package/gluon-announced/Makefile

@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-announced
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-announced
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=announced support
+  DEPENDS:=+gluon-announce
+endef
+
+define Package/gluon-announced/description
+	Gluon community wifi mesh firmware framework: announced support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-announced/install
+	$(CP) ./files/* $(1)/
+	$(INSTALL_DIR) $(1)/usr/bin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-announced $(1)/usr/bin/
+endef
+
+$(eval $(call BuildPackage,gluon-announced))

+ 45 - 0
package/gluon-announced/files/etc/hotplug.d/iface/10-gluon-announced

@@ -0,0 +1,45 @@
+#!/bin/sh
+
+. /usr/share/libubox/jshn.sh
+. /lib/functions/service.sh
+
+DEVLIST=/var/run/gluon-announced.devs
+DAEMON=/usr/bin/gluon-announced
+
+ifname_to_dev () {
+	json_load "$(ubus call network.interface.$1 status)"
+	json_get_var dev device
+
+	echo "$dev"
+}
+
+restart_announced () {
+	SERVICE_USE_PID=1
+	SERVICE_WRITE_PID=1
+	SERVICE_DAEMONIZE=1
+
+	DEVS=$(cat $DEVLIST | while read dev iface;do echo -n " -i $dev";done)
+
+	service_stop $DAEMON
+	service_start $DAEMON -g ff02:0:0:0:0:0:2:1001 -p 1001 -s '/lib/gluon/announce/collect.lua nodeinfo' $DEVS
+}
+
+case "$ACTION" in
+	ifdown)
+		sed -i "/$INTERFACE/d" $DEVLIST
+		;;
+	ifup)
+		DEVICE=$(ifname_to_dev $INTERFACE)
+		MESH=$(cat /sys/class/net/$DEVICE/batman_adv/mesh_iface)
+
+		[ $MESH = "bat0" ] || exit 0
+
+		DEVS="$(cat $DEVLIST; echo $DEVICE $INTERFACE)"
+
+		echo "$DEVS" | sort | uniq > $DEVLIST
+
+		restart_announced
+
+		;;
+esac
+

+ 6 - 0
package/gluon-announced/src/Makefile

@@ -0,0 +1,6 @@
+all: gluon-announced
+
+gluon-announced: gluon-announced.c
+
+clean:
+	rm gluon-announced

+ 221 - 0
package/gluon-announced/src/gluon-announced.c

@@ -0,0 +1,221 @@
+/*
+   Copyright (c) 2014, Nils Schneider <nils@nilsschneider.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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+void usage() {
+  puts("Usage: gluon-announced [-h] -g <group> -p <port> -i <if0> [-i <if1> ..] -s <script>");
+  puts("  -g <ip6>         multicast group, e.g. ff02:0:0:0:0:0:2:1001");
+  puts("  -p <int>         port number to listen on");
+  puts("  -i <string>      interface on which the group is joined");
+  puts("  -s <string>      script to be executed for each request");
+  puts("  -h               this help\n");
+}
+
+/* The maximum size of output returned is limited to 8192 bytes (including
+ * terminating null byte) for now. If this turns out to be problem, a
+ * dynamic buffer should be implemented instead of increasing the
+ * limit.
+ */
+#define BUFFER 8192
+
+char *run_script(size_t *length, const char *script) {
+  FILE *f;
+  char *buffer;
+
+  buffer = calloc(BUFFER, sizeof(char));
+
+  if (buffer == NULL) {
+    fprintf(stderr, "couldn't allocate buffer\n");
+    return NULL;
+  }
+
+  f = popen(script, "r");
+
+  size_t read_bytes = 0;
+  while (1) {
+    ssize_t ret = fread(buffer+read_bytes, sizeof(char), BUFFER-read_bytes, f);
+
+    if (ret <= 0)
+      break;
+
+    read_bytes += ret;
+  }
+
+  int ret = pclose(f);
+
+  if (ret != 0)
+    fprintf(stderr, "script exited with status %d\n", ret);
+
+  *length = read_bytes;
+
+  return buffer;
+}
+
+void join_mcast(const int sock, const struct in6_addr addr, const char *iface) {
+  struct ipv6_mreq mreq;
+
+  mreq.ipv6mr_multiaddr = addr;
+  mreq.ipv6mr_interface = if_nametoindex(iface);
+
+  if (mreq.ipv6mr_interface == 0)
+    goto error;
+
+  if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+    goto error;
+
+  return;
+
+error:
+  fprintf(stderr, "Could not join multicast group on %s: ", iface);
+  perror(NULL);
+  return;
+}
+
+#define REQUESTSIZE 64
+
+char *recvrequest(const int sock, struct sockaddr *client_addr, socklen_t *clilen) {
+  char request_buffer[REQUESTSIZE];
+  ssize_t read_bytes;
+
+  read_bytes = recvfrom(sock, request_buffer, sizeof(request_buffer), 0, client_addr, clilen);
+
+  if (read_bytes < 0) {
+    perror("recvfrom failed");
+    exit(EXIT_FAILURE);
+  }
+
+  char *request = strndup(request_buffer, read_bytes);
+
+  if (request == NULL)
+    perror("Could not receive request");
+
+  return strsep(&request, "\r\n\t ");
+}
+
+void serve(const int sock, const char *script) {
+  char *request;
+  socklen_t clilen;
+  struct sockaddr_in6 client_addr;
+
+  clilen = sizeof(client_addr);
+
+  while (1) {
+    request = recvrequest(sock, (struct sockaddr*)&client_addr, &clilen);
+
+    int cmp = strcmp(request, "nodeinfo");
+    free(request);
+
+    if (cmp != 0)
+      continue;
+
+    char *msg;
+    size_t msg_length;
+    msg = run_script(&msg_length, script);
+
+    if (sendto(sock, msg, msg_length, 0, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
+      perror("sendto failed");
+      exit(EXIT_FAILURE);
+    }
+
+    free(msg);
+  }
+}
+
+int main(int argc, char **argv) {
+  int sock;
+  struct sockaddr_in6 server_addr = {};
+  char *script = NULL;
+  struct in6_addr mgroup_addr;
+
+  sock = socket(PF_INET6, SOCK_DGRAM, 0);
+
+  if (sock < 0) {
+    perror("creating socket");
+    exit(EXIT_FAILURE);
+  }
+
+  server_addr.sin6_family = AF_INET6;
+  server_addr.sin6_addr = in6addr_any;
+
+  opterr = 0;
+
+  int group_set = 0;
+
+  int c;
+  while ((c = getopt(argc, argv, "p:g:s:i:h")) != -1)
+    switch (c) {
+      case 'p':
+        server_addr.sin6_port = htons(atoi(optarg));
+        break;
+      case 'g':
+        if (!inet_pton(AF_INET6, optarg, &mgroup_addr)) {
+          perror("Invalid multicast group. This message will probably confuse you");
+          exit(EXIT_FAILURE);
+        }
+
+        group_set = 1;
+        break;
+      case 's':
+        script = optarg;
+
+        break;
+      case 'i':
+        if (!group_set) {
+          fprintf(stderr, "Multicast group must be given before interface.\n");
+          exit(EXIT_FAILURE);
+        }
+        join_mcast(sock, mgroup_addr, optarg);
+        break;
+      case 'h':
+        usage();
+        exit(EXIT_SUCCESS);
+        break;
+      default:
+        fprintf(stderr, "Invalid parameter %c ignored.\n", c);
+    }
+
+  if (script == NULL) {
+    fprintf(stderr, "No script given\n");
+    exit(EXIT_FAILURE);
+  }
+
+  if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
+    perror("bind failed");
+    exit(EXIT_FAILURE);
+  }
+
+  serve(sock, script);
+
+  return EXIT_FAILURE;
+}

+ 36 - 0
package/gluon-authorized-keys/Makefile

@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-authorized-keys
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-authorized-keys
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Fill /etc/dropbear/authorized_keys from site.conf
+  DEPENDS:=+gluon-core
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-authorized-keys/install
+	$(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-authorized-keys/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-authorized-keys))

+ 1 - 0
package/gluon-authorized-keys/check_site.lua

@@ -0,0 +1 @@
+need_string_array 'authorized_keys'

+ 22 - 0
package/gluon-authorized-keys/files/lib/gluon/upgrade/100-authorized-keys

@@ -0,0 +1,22 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local file = '/etc/dropbear/authorized_keys'
+
+local keys = {}
+
+function load_keys()
+  for line in io.lines(file) do
+    keys[line] = true
+  end
+end
+
+pcall(load_keys)
+
+local f = io.open(file, 'a')
+for _, key in ipairs(site.authorized_keys) do
+  if not keys[key] then
+    f:write(key .. '\n')
+  end
+end
+f:close()

+ 42 - 0
package/gluon-autoupdater/Makefile

@@ -0,0 +1,42 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-autoupdater
+PKG_VERSION:=4
+PKG_RELEASE:=$(GLUON_BRANCH)
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-autoupdater
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  DEPENDS:=+gluon-core +gluon-cron +autoupdater
+  TITLE:=Automatically update firmware
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-autoupdater/install
+	$(CP) ./files/* $(1)/
+
+	if [ '$(GLUON_BRANCH)' ]; then \
+		$(INSTALL_DIR) $(1)/lib/gluon/autoupdater; \
+		echo '$(GLUON_BRANCH)' > $(1)/lib/gluon/autoupdater/default_branch; \
+	fi
+endef
+
+define Package/gluon-autoupdater/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-autoupdater))

+ 12 - 0
package/gluon-autoupdater/check_site.lua

@@ -0,0 +1,12 @@
+need_string 'autoupdater.branch'
+
+local function check_branch(k, _)
+   local prefix = string.format('autoupdater.branches[%q].', k)
+
+   need_string(prefix .. 'name')
+   need_string_array(prefix .. 'mirrors')
+   need_number(prefix .. 'good_signatures')
+   need_string_array(prefix .. 'pubkeys')
+end
+
+need_table('autoupdater.branches', check_branch)

+ 7 - 0
package/gluon-autoupdater/files/lib/gluon/announce/nodeinfo.d/software/autoupdater

@@ -0,0 +1,7 @@
+local autoupdater = uci:get_all('autoupdater', 'settings')
+if autoupdater then
+	return {
+		branch = autoupdater['branch'],
+		enabled = uci:get_bool('autoupdater', 'settings', 'enabled'),
+	}
+end

+ 57 - 0
package/gluon-autoupdater/files/lib/gluon/upgrade/500-autoupdater

@@ -0,0 +1,57 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+
+for name, config in pairs(site.autoupdater.branches) do
+	c:delete('autoupdater', name)
+	c:section('autoupdater', 'branch', name,
+		  {
+			  name = config.name,
+			  mirror = config.mirrors,
+			  good_signatures = config.good_signatures,
+			  pubkey = config.pubkeys,
+		  }
+	)
+end
+
+if not c:get('autoupdater', 'settings') then
+	local enabled = 0
+	local branch = site.autoupdater.branch
+
+	local f = io.open('/lib/gluon/autoupdater/default_branch')
+	if f then
+		enabled = 1
+		branch = f:read('*line')
+		f:close()
+	end
+
+	c:section('autoupdater', 'autoupdater', 'settings',
+		  {
+			  enabled = enabled,
+			  branch = branch,
+		  }
+	)
+end
+
+c:set('autoupdater', 'settings', 'version_file', '/lib/gluon/release')
+
+c:save('autoupdater')
+c:commit('autoupdater')
+
+
+local autoupdater_util = require 'autoupdater.util'
+autoupdater_util.randomseed()
+
+
+-- Perform updates at a random time between 04:00 and 05:00, and once an hour
+-- a fallback update (used after the regular updates haven't
+local minute = math.random(0, 59)
+
+local f = io.open('/lib/gluon/cron/autoupdater', 'w')
+f:write(string.format('%i 4 * * * /usr/sbin/autoupdater\n', minute))
+f:write(string.format('%i 0-3,5-23 * * * /usr/sbin/autoupdater --fallback\n', minute))
+f:close()

+ 40 - 0
package/gluon-config-mode-autoupdater/Makefile

@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-autoupdater
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-autoupdater
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Let the user know whether the autoupdater is enabled or not.
+  DEPENDS:=+gluon-config-mode-core +gluon-autoupdater
+endef
+
+define Package/gluon-config-mode-autoupdater/description
+	Luci based config mode
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-autoupdater,i18n)
+endef
+
+define Package/gluon-config-mode-autoupdater/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-autoupdater,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-autoupdater))

+ 19 - 0
package/gluon-config-mode-autoupdater/files/lib/gluon/config-mode/wizard/0050-autoupdater-info.lua

@@ -0,0 +1,19 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local enabled = uci:get_bool("autoupdater", "settings", "enabled")
+  if enabled then
+    local s = form:section(cbi.SimpleSection, nil,
+      i18n.translate('This node will automatically update its firmware when a new version is available.'))
+  end
+end
+
+function M.handle(data)
+  return
+end
+
+return M

+ 17 - 0
package/gluon-config-mode-autoupdater/i18n/de.po

@@ -0,0 +1,17 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-18 16:03+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid ""
+"This node will automatically update its firmware when a new version is "
+"available."
+msgstr "Dieser Knoten aktualisiert seine Firmware automatisch, sobald "
+"eine neue Version vorliegt."

+ 7 - 0
package/gluon-config-mode-autoupdater/i18n/gluon-config-mode-autoupdater.pot

@@ -0,0 +1,7 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid ""
+"This node will automatically update its firmware when a new version is "
+"available."
+msgstr ""

+ 36 - 0
package/gluon-config-mode-contact-info/Makefile

@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-contact-info
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-contact-info
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Set a custom string that will be distributed in the mesh.
+  DEPENDS:=+gluon-config-mode-core +gluon-node-info
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-contact-info,i18n)
+endef
+
+define Package/gluon-config-mode-contact-info/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-contact-info,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-contact-info))

+ 34 - 0
package/gluon-config-mode-contact-info/files/lib/gluon/config-mode/wizard/0500-contact-info.lua

@@ -0,0 +1,34 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local s = form:section(cbi.SimpleSection, nil, i18n.translate(
+    'You can provide your contact information here to '
+      .. 'allow others to contact you. Please note that '
+      .. 'this information will be visible <em>publicly</em> '
+      .. 'on the internet together with your node\'s coordinates.'
+    )
+  )
+
+  local o = s:option(cbi.Value, "_contact", i18n.translate("Contact info"))
+  o.default = uci:get_first("gluon-node-info", "owner", "contact", "")
+  o.rmempty = true
+  o.datatype = "string"
+  o.description = i18n.translate("e.g. E-mail or phone number")
+  o.maxlen = 140
+end
+
+function M.handle(data)
+  if data._contact ~= nil then
+    uci:set("gluon-node-info", uci:get_first("gluon-node-info", "owner"), "contact", data._contact)
+  else
+    uci:delete("gluon-node-info", uci:get_first("gluon-node-info", "owner"), "contact")
+  end
+  uci:save("gluon-node-info")
+  uci:commit("gluon-node-info")
+end
+
+return M

+ 27 - 0
package/gluon-config-mode-contact-info/i18n/de.po

@@ -0,0 +1,27 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 01:32+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "Contact info"
+msgstr "Kontakt"
+
+msgid ""
+"You can provide your contact information here to allow others to contact "
+"you. Please note that this information will be visible <em>publicly</em> on "
+"the internet together with your node's coordinates."
+msgstr ""
+"Hier kannst du einen <em>öffentlichen</em> Hinweis hinterlegen, um anderen "
+"zu ermöglichen, Kontakt mit dir aufzunehmen. Bitte beachte, dass "
+"dieser Hinweis auch öffentlich im Internet, zusammen mit den Koordinaten "
+"deines Knotens, einsehbar sein wird."
+
+msgid "e.g. E-mail or phone number"
+msgstr "z.B. E-Mail oder Telefonnummer"

+ 14 - 0
package/gluon-config-mode-contact-info/i18n/gluon-config-mode-contact-info.pot

@@ -0,0 +1,14 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Contact info"
+msgstr ""
+
+msgid ""
+"You can provide your contact information here to allow others to contact "
+"you. Please note that this information will be visible <em>publicly</em> on "
+"the internet together with your node's coordinates."
+msgstr ""
+
+msgid "e.g. E-mail or phone number"
+msgstr ""

+ 39 - 0
package/gluon-config-mode-core/Makefile

@@ -0,0 +1,39 @@
+# Copyright (C) 2012 Nils Schneider <nils at nilsschneider.net>
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-core
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-core
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Luci based config mode for user friendly setup of new mesh nodes
+  DEPENDS:=+gluon-setup-mode +gluon-luci-theme +gluon-lock-password $(GLUON_I18N_PACKAGES)
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-core,i18n)
+endef
+
+define Package/gluon-config-mode-core/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-core,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-core))

+ 3 - 0
package/gluon-config-mode-core/files/lib/gluon/config-mode/reboot/0900-msg-reboot.lua

@@ -0,0 +1,3 @@
+local i18n = require 'luci.i18n'
+
+return function () luci.template.render_string(i18n.translate('gluon-config-mode:reboot')) end

+ 89 - 0
package/gluon-config-mode-core/files/usr/lib/lua/luci/controller/gluon-config-mode/index.lua

@@ -0,0 +1,89 @@
+--[[
+Copyright 2013 Nils Schneider <nils@nilsschneider.net>
+
+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
+
+$Id$
+]]--
+
+module("luci.controller.gluon-config-mode.index", package.seeall)
+
+function index()
+  local uci_state = luci.model.uci.cursor_state()
+
+  if uci_state:get_first("gluon-setup-mode", "setup_mode", "running", "0") == "1" then
+    local root = node()
+    if not root.target then
+      root.target = alias("gluon-config-mode")
+      root.index = true
+    end
+
+    page          = node()
+    page.lock     = true
+    page.target   = alias("gluon-config-mode")
+    page.subindex = true
+    page.index    = false
+
+    page          = node("gluon-config-mode")
+    page.title    = _("Wizard")
+    page.target   = alias("gluon-config-mode", "wizard")
+    page.order    = 5
+    page.setuser  = "root"
+    page.setgroup = "root"
+    page.index    = true
+
+    entry({"gluon-config-mode", "wizard"}, form("gluon-config-mode/wizard")).index = true
+    entry({"gluon-config-mode", "reboot"}, call("action_reboot"))
+  end
+end
+
+function action_reboot()
+  local uci = luci.model.uci.cursor()
+
+  uci:set("gluon-setup-mode", uci:get_first("gluon-setup-mode", "setup_mode"), "configured", "1")
+  uci:save("gluon-setup-mode")
+  uci:commit("gluon-setup-mode")
+
+  if nixio.fork() ~= 0 then
+    local fs = require "luci.fs"
+
+    local parts_dir = "/lib/gluon/config-mode/reboot/"
+    local files = fs.dir(parts_dir)
+
+    table.sort(files)
+
+    local parts = {}
+
+    for _, entry in ipairs(files) do
+      if entry:sub(1, 1) ~= '.' then
+        local f = dofile(parts_dir .. '/' .. entry)
+        if f ~= nil then
+          table.insert(parts, f)
+        end
+      end
+    end
+
+    local hostname = uci:get_first("system", "system", "hostname")
+
+    luci.template.render("gluon-config-mode/reboot", { parts=parts
+                                                     , hostname=hostname
+                                                     })
+  else
+    debug.setfenv(io.stdout, debug.getfenv(io.open '/dev/null'))
+    io.stdout:close()
+
+    -- Sleep a little so the browser can fetch everything required to
+    -- display the reboot page, then reboot the device.
+    nixio.nanosleep(2)
+
+    -- Run reboot with popen so it gets its own std filehandles.
+    io.popen("reboot")
+
+    -- Prevent any further execution in this child.
+    os.exit()
+  end
+end

+ 38 - 0
package/gluon-config-mode-core/files/usr/lib/lua/luci/model/cbi/gluon-config-mode/wizard.lua

@@ -0,0 +1,38 @@
+local wizard_dir = "/lib/gluon/config-mode/wizard/"
+local i18n = luci.i18n
+local uci = luci.model.uci.cursor()
+local fs = require "luci.fs"
+local f, s
+
+local wizard = {}
+local files = fs.dir(wizard_dir)
+
+table.sort(files)
+
+for _, entry in ipairs(files) do
+  if entry:sub(1, 1) ~= '.' then
+    table.insert(wizard, dofile(wizard_dir .. '/' .. entry))
+  end
+end
+
+f = SimpleForm("wizard")
+f.reset = false
+f.template = "gluon-config-mode/cbi/wizard"
+
+for _, s in ipairs(wizard) do
+  s.section(f)
+end
+
+function f.handle(self, state, data)
+  if state == FORM_VALID then
+    for _, s in ipairs(wizard) do
+      s.handle(data)
+    end
+
+    luci.http.redirect(luci.dispatcher.build_url("gluon-config-mode", "reboot"))
+  end
+
+  return true
+end
+
+return f

+ 46 - 0
package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/cbi/wizard.htm

@@ -0,0 +1,46 @@
+<%-
+	local sysconfig = require 'gluon.sysconfig'
+	local i18n = require 'luci.i18n'
+	local template = require 'luci.template'
+-%>
+
+<h2><%:Welcome!%></h2>
+<p>
+	<%= template.render_string(i18n.translate('gluon-config-mode:welcome'), {hostname=hostname, sysconfig=sysconfig}) %>
+</p>
+
+<% if not self.embedded then %>
+<form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>">
+	<div>
+		<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+		<input type="hidden" name="cbi.submit" value="1" />
+	</div>
+<% end %>
+	<div class="cbi-map" id="cbi-<%=self.config%>">
+		<% if self.title and #self.title > 0 then %><h2><a id="content" name="content"><%=self.title%></a></h2><% end %>
+		<% if self.description and #self.description > 0 then %><div class="cbi-map-descr"><%=self.description%></div><% end %>
+		<% self:render_children() %>
+		<br />
+	</div>
+<%- if self.message then %>
+	<div><%=self.message%></div>
+<%- end %>
+<%- if self.errmessage then %>
+	<div class="error"><%=self.errmessage%></div>
+<%- end %>
+<% if not self.embedded then %>
+	<div class="cbi-page-actions">
+<%-
+		if type(self.hidden) == "table" then
+		  for k, v in pairs(self.hidden) do
+-%>
+		<input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" />
+<%-
+		  end
+		end
+%>
+		<input class="cbi-button cbi-button-save" type="submit" value="<%:Save & restart%>" />
+		<script type="text/javascript">cbi_d_update();</script>
+	</div>
+</form>
+<% end %>

+ 17 - 0
package/gluon-config-mode-core/files/usr/lib/lua/luci/view/gluon-config-mode/reboot.htm

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%=luci.i18n.context.lang%>" lang="<%=luci.i18n.context.lang%>">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <title><%=hostname%> is rebooting</title>
+    <link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
+  </head>
+  <body>
+    <div id="maincontainer">
+      <div id="maincontent">
+        <h2><%:Your node's setup is now complete.%></h2>
+        <% for k, v in ipairs(parts) do v() end %>
+      </div>
+    </div>
+  </body>
+</html>

+ 24 - 0
package/gluon-config-mode-core/i18n/de.po

@@ -0,0 +1,24 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 02:07+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#, fuzzy
+msgid "Save & restart"
+msgstr "Speichern & Neustarten"
+
+msgid "Welcome!"
+msgstr "Willkommen!"
+
+msgid "Wizard"
+msgstr "Wizard"
+
+msgid "Your node's setup is now complete."
+msgstr "Dein Knoten ist nun fertig eingerichtet."

+ 14 - 0
package/gluon-config-mode-core/i18n/gluon-config-mode-core.pot

@@ -0,0 +1,14 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Save & restart"
+msgstr ""
+
+msgid "Welcome!"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Your node's setup is now complete."
+msgstr ""

+ 36 - 0
package/gluon-config-mode-geo-location/Makefile

@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-geo-location
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-geo-location
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Set geographic location of a node
+  DEPENDS:=+gluon-config-mode-core +gluon-node-info
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-geo-location,i18n)
+endef
+
+define Package/gluon-config-mode-geo-location/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-geo-location,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-geo-location))

+ 60 - 0
package/gluon-config-mode-geo-location/files/lib/gluon/config-mode/wizard/0400-geo-location.lua

@@ -0,0 +1,60 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local s = form:section(cbi.SimpleSection, nil, i18n.translate(
+    'If you want the location of your node to be displayed on the map, '
+      .. 'you can enter its coordinates here. Specifying the altitude '
+      .. 'is optional and should only be done if a proper value is known.'))
+
+
+  local o
+
+  o = s:option(cbi.Flag, "_location", i18n.translate("Show node on the map"))
+  o.default = uci:get_first("gluon-node-info", "location", "share_location", o.disabled)
+  o.rmempty = false
+
+  o = s:option(cbi.Value, "_latitude", i18n.translate("Latitude"))
+  o.default = uci:get_first("gluon-node-info", "location", "latitude")
+  o:depends("_location", "1")
+  o.rmempty = false
+  o.datatype = "float"
+  o.description = i18n.translatef("e.g. %s", "53.873621")
+
+  o = s:option(cbi.Value, "_longitude", i18n.translate("Longitude"))
+  o.default = uci:get_first("gluon-node-info", "location", "longitude")
+  o:depends("_location", "1")
+  o.rmempty = false
+  o.datatype = "float"
+  o.description = i18n.translatef("e.g. %s", "10.689901")
+
+  o = s:option(cbi.Value, "_altitude", i18n.translate("Altitude"))
+  o.default = uci:get_first("gluon-node-info", "location", "altitude")
+  o:depends("_location", "1")
+  o.rmempty = true
+  o.datatype = "float"
+  o.description = i18n.translatef("e.g. %s", "11.51")
+
+end
+
+function M.handle(data)
+  local sname = uci:get_first("gluon-node-info", "location")
+
+  uci:set("gluon-node-info", sname, "share_location", data._location)
+  if data._location and data._latitude ~= nil and data._longitude ~= nil then
+    uci:set("gluon-node-info", sname, "latitude", data._latitude)
+    uci:set("gluon-node-info", sname, "longitude", data._longitude)
+    if data._altitude ~= nil then
+      uci:set("gluon-node-info", sname, "altitude", data._altitude)
+    else
+      uci:delete("gluon-node-info", sname, "altitude")
+    end
+  end
+  uci:save("gluon-node-info")
+  uci:commit("gluon-node-info")
+end
+
+return M

+ 36 - 0
package/gluon-config-mode-geo-location/i18n/de.po

@@ -0,0 +1,36 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: gluon-config-mode-geo-location\n"
+"PO-Revision-Date: 2015-03-23 02:18+0100\n"
+"Last-Translator: Martin Weinelt <martin@darmstadt.freifunk.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid ""
+"If you want the location of your node to be displayed on the map, you can "
+"enter its coordinates here. Specifying the altitude is optional and should "
+"only be done if a proper value is known."
+msgstr ""
+"Um deinen Knoten auf der Karte anzeigen zu können, benötigen wir seine "
+"Koordinaten. Hier hast du die Möglichkeit, diese zu hinterlegen. Die "
+"Höhenangabe ist optional und sollte nur gesetzt werden, wenn ein exakter "
+"Wert bekannt ist."
+
+msgid "Latitude"
+msgstr "Breitengrad"
+
+msgid "Longitude"
+msgstr "Längengrad"
+
+msgid "Altitude"
+msgstr "Höhenmeter über Normalnull"
+
+msgid "Show node on the map"
+msgstr "Knoten auf der Karte anzeigen"
+
+msgid "e.g. %s"
+msgstr "z.B. %s"

+ 20 - 0
package/gluon-config-mode-geo-location/i18n/gluon-config-mode-geo-location.pot

@@ -0,0 +1,20 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid ""
+"If you want the location of your node to be displayed on the map, you can "
+"enter its coordinates here. Specifying the altitude is optional and should "
+"only be done if a proper value is known."
+msgstr ""
+
+msgid "Latitude"
+msgstr ""
+
+msgid "Longitude"
+msgstr ""
+
+msgid "Show node on the map"
+msgstr ""
+
+msgid "e.g. %s"
+msgstr ""

+ 36 - 0
package/gluon-config-mode-hostname/Makefile

@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-hostname
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-hostname
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Set the hostname
+  DEPENDS:=+gluon-config-mode-core
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-hostname,i18n)
+endef
+
+define Package/gluon-config-mode-hostname/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-hostname,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-hostname))

+ 21 - 0
package/gluon-config-mode-hostname/files/lib/gluon/config-mode/wizard/0100-hostname.lua

@@ -0,0 +1,21 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local s = form:section(cbi.SimpleSection, nil, nil)
+  local o = s:option(cbi.Value, "_hostname", i18n.translate("Node name"))
+  o.value = uci:get_first("system", "system", "hostname")
+  o.rmempty = false
+  o.datatype = "hostname"
+end
+
+function M.handle(data)
+  uci:set("system", uci:get_first("system", "system"), "hostname", data._hostname)
+  uci:save("system")
+  uci:commit("system")
+end
+
+return M

+ 14 - 0
package/gluon-config-mode-hostname/i18n/de.po

@@ -0,0 +1,14 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 00:54+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "Node name"
+msgstr "Name dieses Knotens"

+ 5 - 0
package/gluon-config-mode-hostname/i18n/gluon-config-mode-hostname.pot

@@ -0,0 +1,5 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Node name"
+msgstr ""

+ 36 - 0
package/gluon-config-mode-mesh-vpn/Makefile

@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-config-mode-mesh-vpn
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
+
+
+define Package/gluon-config-mode-mesh-vpn
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Toggle mesh-vpn and bandwidth limit
+  DEPENDS:=+gluon-config-mode-core +gluon-mesh-vpn-fastd +gluon-simple-tc
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	$(call GluonBuildI18N,gluon-config-mode-mesh-vpn,i18n)
+endef
+
+define Package/gluon-config-mode-mesh-vpn/install
+	$(CP) ./files/* $(1)/
+	$(call GluonInstallI18N,gluon-config-mode-mesh-vpn,$(1))
+endef
+
+$(eval $(call BuildPackage,gluon-config-mode-mesh-vpn))

+ 29 - 0
package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/reboot/0100-mesh-vpn.lua

@@ -0,0 +1,29 @@
+local uci = luci.model.uci.cursor()
+local meshvpn_enabled = uci:get("fastd", "mesh_vpn", "enabled", "0")
+
+if meshvpn_enabled ~= "1" then
+  return nil
+else
+  local i18n = require "luci.i18n"
+  local util = require "luci.util"
+  local site = require 'gluon.site_config'
+  local sysconfig = require 'gluon.sysconfig'
+
+  local pubkey = util.trim(util.exec("/etc/init.d/fastd show_key " .. "mesh_vpn"))
+  local hostname = uci:get_first("system", "system", "hostname")
+
+  local msg = [[<p>]] .. i18n.translate('gluon-config-mode:pubkey') .. [[</p>
+               <div class="the-key">
+                 # <%= hostname %>
+                 <br/>
+               <%= pubkey %>
+               </div>]]
+
+  return function ()
+           luci.template.render_string(msg, { pubkey=pubkey
+                                            , hostname=hostname
+                                            , site=site
+                                            , sysconfig=sysconfig
+                                            })
+         end
+end

+ 64 - 0
package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua

@@ -0,0 +1,64 @@
+local cbi = require "luci.cbi"
+local i18n = require "luci.i18n"
+local uci = luci.model.uci.cursor()
+
+local M = {}
+
+function M.section(form)
+  local msg = i18n.translate('Your internet connection can be used to establish an ' ..
+                             'encrypted connection with other nodes. ' ..
+                             'Enable this option if there are no other nodes reachable ' ..
+                             'over WLAN in your vicinity or you want to make a part of ' ..
+                             'your connection\'s bandwidth available for the network. You can limit how ' ..
+                             'much bandwidth the node will use at most.')
+  local s = form:section(cbi.SimpleSection, nil, msg)
+
+  local o
+
+  o = s:option(cbi.Flag, "_meshvpn", i18n.translate("Use internet connection (mesh VPN)"))
+  o.default = uci:get_bool("fastd", "mesh_vpn", "enabled") and o.enabled or o.disabled
+  o.rmempty = false
+
+  o = s:option(cbi.Flag, "_limit_enabled", i18n.translate("Limit bandwidth"))
+  o:depends("_meshvpn", "1")
+  o.default = uci:get_bool("gluon-simple-tc", "mesh_vpn", "enabled") and o.enabled or o.disabled
+  o.rmempty = false
+
+  o = s:option(cbi.Value, "_limit_ingress", i18n.translate("Downstream (kbit/s)"))
+  o:depends("_limit_enabled", "1")
+  o.value = uci:get("gluon-simple-tc", "mesh_vpn", "limit_ingress")
+  o.rmempty = false
+  o.datatype = "integer"
+
+  o = s:option(cbi.Value, "_limit_egress", i18n.translate("Upstream (kbit/s)"))
+  o:depends("_limit_enabled", "1")
+  o.value = uci:get("gluon-simple-tc", "mesh_vpn", "limit_egress")
+  o.rmempty = false
+  o.datatype = "integer"
+end
+
+function M.handle(data)
+  uci:set("fastd", "mesh_vpn", "enabled", data._meshvpn)
+  uci:save("fastd")
+  uci:commit("fastd")
+
+  -- checks for nil needed due to o:depends(...)
+  if data._limit_enabled ~= nil then
+    uci:set("gluon-simple-tc", "mesh_vpn", "interface")
+    uci:set("gluon-simple-tc", "mesh_vpn", "enabled", data._limit_enabled)
+    uci:set("gluon-simple-tc", "mesh_vpn", "ifname", "mesh-vpn")
+
+    if data._limit_ingress ~= nil then
+      uci:set("gluon-simple-tc", "mesh_vpn", "limit_ingress", data._limit_ingress)
+    end
+
+    if data._limit_egress ~= nil then
+      uci:set("gluon-simple-tc", "mesh_vpn", "limit_egress", data._limit_egress)
+    end
+
+    uci:commit("gluon-simple-tc")
+    uci:commit("gluon-simple-tc")
+  end
+end
+
+return M

+ 36 - 0
package/gluon-config-mode-mesh-vpn/i18n/de.po

@@ -0,0 +1,36 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"PO-Revision-Date: 2015-03-19 22:05+0100\n"
+"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "Downstream (kbit/s)"
+msgstr "Downstream (kbit/s)"
+
+msgid "Limit bandwidth"
+msgstr "Bandbreite begrenzen"
+
+msgid "Upstream (kbit/s)"
+msgstr "Upstream (kbit/s)"
+
+msgid "Use internet connection (mesh VPN)"
+msgstr "Internetverbindung nutzen (Mesh-VPN)"
+
+msgid ""
+"Your internet connection can be used to establish an encrypted connection "
+"with other nodes. Enable this option if there are no other nodes reachable "
+"over WLAN in your vicinity or you want to make a part of your connection's "
+"bandwidth available for the network. You can limit how much bandwidth the "
+"node will use at most."
+msgstr ""
+"Dein Knoten kann deine Internetverbindung nutzen um darüber eine "
+"verschlüsselte Verbindung zu anderen Knoten aufzubauen. Die dafür "
+"genutzte Bandbreite kannst du beschränken. Aktiviere die Option, falls keine "
+"per WLAN erreichbaren Nachbarknoten in deiner Nähe sind oder du deine "
+"Internetverbindung für das Mesh-Netzwerk zur Verfügung stellen möchtest."

+ 22 - 0
package/gluon-config-mode-mesh-vpn/i18n/gluon-config-mode-mesh-vpn.pot

@@ -0,0 +1,22 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "Downstream (kbit/s)"
+msgstr ""
+
+msgid "Limit bandwidth"
+msgstr ""
+
+msgid "Upstream (kbit/s)"
+msgstr ""
+
+msgid "Use internet connection (mesh VPN)"
+msgstr ""
+
+msgid ""
+"Your internet connection can be used to establish an encrypted connection "
+"with other nodes. Enable this option if there are no other nodes reachable "
+"over WLAN in your vicinity or you want to make a part of your connection's "
+"bandwidth available for the network. You can limit how much bandwidth the "
+"node will use at most."
+msgstr ""

+ 59 - 0
package/gluon-core/Makefile

@@ -0,0 +1,59 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-core
+PKG_VERSION:=3
+PKG_RELEASE:=$(GLUON_VERSION)
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-core
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Base files of Gluon
+  DEPENDS:=+gluon-site +lua-platform-info +luci-lib-nixio +odhcp6c +firewall
+endef
+
+
+define LangConfig
+config GLUON_LANG_$(1)
+	bool "$(GLUON_LANG_$(1)) language support"
+	depends on PACKAGE_gluon-core
+	default n
+
+endef
+
+
+define Package/gluon-core/config
+$(foreach lang,$(GLUON_SUPPORTED_LANGS),$(call LangConfig,$(lang)))
+endef
+
+
+define Package/gluon-core/description
+	Gluon community wifi mesh firmware framework: core
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-core/install
+	$(CP) ./files/* $(1)/
+
+	$(INSTALL_DIR) $(1)/lib/gluon
+	echo "$(GLUON_VERSION)" > $(1)/lib/gluon/gluon-version
+endef
+
+define Package/gluon-core/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-core))

+ 10 - 0
package/gluon-core/check_site.lua

@@ -0,0 +1,10 @@
+need_string 'site_code'
+need_string 'site_name'
+
+need_string('hostname_prefix', false)
+need_string 'timezone'
+
+need_string_array('ntp_servers', false)
+
+need_string_match('prefix4', '^%d+.%d+.%d+.%d+/%d+$')
+need_string_match('prefix6', '^[%x:]+/%d+$')

+ 5 - 0
package/gluon-core/files/etc/uci-defaults/zzz-gluon-upgrade

@@ -0,0 +1,5 @@
+#!/bin/sh
+
+for script in /lib/gluon/upgrade/*; do
+	"$script"
+done

+ 0 - 0
package/gluon-core/files/lib/gluon/core/sysconfig/.keep


+ 10 - 0
package/gluon-core/files/lib/gluon/upgrade/001-upgrade

@@ -0,0 +1,10 @@
+#!/usr/bin/lua
+
+local fs = require 'luci.fs'
+local sysconfig = require 'gluon.sysconfig'
+
+
+if fs.isfile('/lib/gluon/version/core') and not sysconfig.gluon_version then
+  -- This isn't an initial upgrade, so set gluon_version
+  sysconfig.gluon_version = ''
+end

+ 42 - 0
package/gluon-core/files/lib/gluon/upgrade/010-primary-mac

@@ -0,0 +1,42 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+
+if sysconfig.primary_mac then
+  os.exit(0)
+end
+
+
+local platform = require 'gluon.platform'
+
+local fs = require 'luci.fs'
+local util = require 'luci.util'
+
+
+local try_files = {
+  '/sys/class/ieee80211/phy0/macaddress',
+  '/sys/class/net/eth0/address',
+}
+
+if platform.match('ar71xx', 'generic', {'tl-wdr3600', 'tl-wdr4300'}) then
+  table.insert(try_files, 1, '/sys/class/ieee80211/phy1/macaddress')
+end
+
+if platform.match('ar71xx', 'generic', {'unifi-outdoor-plus'}) then
+  table.insert(try_files, 1, '/sys/class/net/eth0/address')
+end
+
+if platform.match('ar71xx', 'generic', {'archer-c5', 'archer-c7'}) then
+  table.insert(try_files, 1, '/sys/class/net/eth1/address')
+end
+
+
+for _, file in ipairs(try_files) do
+  local addr = fs.readfile(file)
+
+  if addr then
+    sysconfig.primary_mac = util.trim(addr)
+    break
+  end
+end

+ 36 - 0
package/gluon-core/files/lib/gluon/upgrade/020-interfaces

@@ -0,0 +1,36 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+local gluon_util = require 'gluon.util'
+local platform = require 'gluon.platform'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if not (sysconfig.lan_ifname or sysconfig.wan_ifname) then
+  local function iface_exists(name)
+    return (gluon_util.exec('ip', 'link', 'show', 'dev', (name:gsub('%..*$', ''))) == 0)
+  end
+
+
+  local lan_ifname = uci:get('network', 'lan', 'ifname')
+  local wan_ifname = uci:get('network', 'wan', 'ifname')
+
+  if platform.match('ar71xx', 'generic', {'cpe510', 'nanostation-m', 'nanostation-m-xw', 'unifi-outdoor-plus'}) then
+    lan_ifname, wan_ifname = wan_ifname, lan_ifname
+  end
+
+  if wan_ifname and iface_exists(wan_ifname) then
+    sysconfig.wan_ifname = wan_ifname
+    sysconfig.lan_ifname = lan_ifname
+  else
+    sysconfig.wan_ifname = lan_ifname
+  end
+
+
+  uci:delete('network', 'lan')
+  uci:delete('network', 'wan')
+
+  uci:save('network')
+  uci:commit('network')
+end

+ 18 - 0
package/gluon-core/files/lib/gluon/upgrade/030-system

@@ -0,0 +1,18 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+-- Initial
+if not sysconfig.gluon_version then
+  local site = require 'gluon.site_config'
+  local util = require 'gluon.util'
+  local uci = require('luci.model.uci').cursor()
+
+  local system = uci:get_first('system', 'system')
+
+  uci:set('system', system, 'hostname', (site.hostname_prefix or '') .. util.node_id())
+  uci:set('system', system, 'timezone', site.timezone)
+
+  uci:save('system')
+  uci:commit('system')
+end

+ 5 - 0
package/gluon-core/files/lib/gluon/upgrade/100-dnsmasq

@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ -e /etc/dnsmasq.conf ]; then
+	sed -i -e '/^conf-dir=\/lib\/gluon\/dnsmasq\.d$/d' -e '/^conf-dir=\/var\/gluon\/dnsmasq\.d$/d' /etc/dnsmasq.conf
+fi

+ 58 - 0
package/gluon-core/files/lib/gluon/upgrade/110-network

@@ -0,0 +1,58 @@
+#!/usr/bin/lua
+
+local uci = require('luci.model.uci').cursor()
+local sysctl = require 'gluon.sysctl'
+local sysconfig = require 'gluon.sysconfig'
+
+
+uci:section('network', 'interface', 'wan',
+	    {
+	      ifname = sysconfig.wan_ifname,
+	      type = 'bridge',
+	      peerdns = 0,
+	      auto = 1,
+	    }
+)
+
+if not uci:get('network', 'wan', 'proto') then
+  uci:set('network', 'wan', 'proto', 'dhcp')
+end
+
+
+uci:section('network', 'interface', 'wan6',
+	    {
+	      ifname = 'br-wan',
+	      peerdns = 0,
+	      ip6table = 1,
+	    }
+)
+
+if not uci:get('network', 'wan6', 'proto') then
+  uci:set('network', 'wan6', 'proto', 'dhcpv6')
+end
+
+
+uci:section('network', 'rule6', 'wan6_lookup',
+	    {
+	       mark = '0x01/0x01',
+	       lookup = 1,
+	    }
+)
+
+uci:section('network', 'route6', 'wan6_unreachable',
+	    {
+	       type = 'unreachable',
+	       interface = 'loopback',
+	       target = '::/0',
+	       gateway = '::',
+	       table = 1,
+	       metric = 65535,
+	    }
+)
+
+uci:save('network')
+uci:commit('network')
+
+
+sysctl.set('net.ipv6.conf.all.accept_ra', 0)
+sysctl.set('net.ipv6.conf.default.accept_ra', 0)

+ 14 - 0
package/gluon-core/files/lib/gluon/upgrade/120-ntp-servers

@@ -0,0 +1,14 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+if not site.ntp_servers or #site.ntp_servers == 0 then
+	os.exit(0)
+end
+
+local c = uci.cursor()
+c:delete('system', 'ntp', 'server')
+c:set_list('system', 'ntp', 'server', site.ntp_servers)
+c:save('system')
+c:commit('system')

+ 5 - 0
package/gluon-core/files/lib/gluon/upgrade/130-reboot-on-oom

@@ -0,0 +1,5 @@
+#!/usr/bin/lua
+
+local sysctl = require 'gluon.sysctl'
+
+sysctl.set('vm.panic_on_oom', 1)

+ 30 - 0
package/gluon-core/files/lib/gluon/upgrade/140-firewall-rules

@@ -0,0 +1,30 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local uci = require 'luci.model.uci'
+
+local c = uci.cursor()
+
+
+local function reject_input_on_wan(zone)
+	if zone.name == 'wan' then
+		c:set('firewall', zone['.name'], 'input', 'REJECT')
+		c:set('firewall', zone['.name'], 'conntrack', '1')
+	end
+
+	return true
+end
+c:foreach('firewall', 'zone', reject_input_on_wan)
+
+c:section('firewall', 'rule', 'wan_ssh',
+	  {
+		  name = 'wan_ssh',
+		  src = 'wan',
+		  dest_port = '22',
+		  proto = 'tcp',
+		  target = 'ACCEPT',
+	  }
+)
+
+c:save('firewall')
+c:commit('firewall')

+ 12 - 0
package/gluon-core/files/lib/gluon/upgrade/200-wireless

@@ -0,0 +1,12 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+-- Initial
+if not sysconfig.gluon_version then
+  local uci = require('luci.model.uci').cursor()
+
+  uci:delete_all('wireless', 'wifi-iface')
+  uci:save('wireless')
+  uci:commit('wireless')
+end

+ 11 - 0
package/gluon-core/files/lib/gluon/upgrade/999-version

@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local sysconfig = require 'gluon.sysconfig'
+
+local fs = require 'luci.fs'
+local util = require 'luci.util'
+
+
+-- Save the Gluon version in the sysconfig so we know which version we
+-- upgraded from after the next upgrade
+sysconfig.gluon_version = util.trim(fs.readfile('/lib/gluon/gluon-version'))

+ 1 - 0
package/gluon-core/files/lib/upgrade/keep.d/gluon

@@ -0,0 +1 @@
+/lib/gluon/core/sysconfig/

+ 31 - 0
package/gluon-core/files/usr/lib/lua/gluon/platform.lua

@@ -0,0 +1,31 @@
+local platform_info = require 'platform_info'
+local util = require 'luci.util'
+
+local setmetatable = setmetatable
+
+
+module 'gluon.platform'
+
+setmetatable(_M,
+	     {
+		__index = platform_info,
+	     }
+)
+
+function match(target, subtarget, boards)
+   if get_target() ~= target then
+      return false
+   end
+
+   if get_subtarget() ~= subtarget then
+      return false
+   end
+
+   if not util.contains(boards, get_board_name()) then
+      return false
+   end
+
+   return true
+end
+
+

+ 21 - 0
package/gluon-core/files/usr/lib/lua/gluon/site_config.lua

@@ -0,0 +1,21 @@
+local config = os.getenv('GLUON_SITE_CONFIG') or '/lib/gluon/site.conf'
+
+local function loader()
+   coroutine.yield('return ')
+   coroutine.yield(io.open(config):read('*a'))
+end
+
+-- setfenv doesn't work with Lua 5.2 anymore, but we're using 5.1
+local site_config = setfenv(assert(load(coroutine.wrap(loader), 'site.conf')), {})()
+
+local setmetatable = setmetatable
+
+module 'gluon.site_config'
+
+setmetatable(_M,
+	{
+		__index = site_config,
+	}
+)
+
+return _M

+ 34 - 0
package/gluon-core/files/usr/lib/lua/gluon/sysconfig.lua

@@ -0,0 +1,34 @@
+local sysconfigdir = '/lib/gluon/core/sysconfig/'
+
+local function get(_, name)
+	local f = io.open(sysconfigdir .. name)
+	if f then
+		local ret = f:read('*line')
+		f:close()
+		return (ret or '')
+	end
+	return nil
+end
+
+local function set(_, name, val)
+	if val then
+		local f = io.open(sysconfigdir .. name, 'w+')
+		f:write(val)
+		f:close()
+	else
+		os.remove(sysconfigdir .. name)
+	end
+end
+
+local setmetatable = setmetatable
+
+module 'gluon.sysconfig'
+
+setmetatable(_M,
+	{
+		__index = get,
+		__newindex = set,
+	}
+)
+
+return _M

+ 8 - 0
package/gluon-core/files/usr/lib/lua/gluon/sysctl.lua

@@ -0,0 +1,8 @@
+local util = require 'gluon.util'
+
+
+module 'gluon.sysctl'
+
+function set(name, value)
+	util.replace_prefix('/etc/sysctl.conf', name .. '=', name .. '=' .. value .. '\n')
+end

+ 33 - 0
package/gluon-core/files/usr/lib/lua/gluon/users.lua

@@ -0,0 +1,33 @@
+local util = require 'gluon.util'
+
+local os = os
+local string = string
+
+
+module 'gluon.users'
+
+function add_user(username, uid, gid)
+	util.lock('/var/lock/passwd')
+	util.replace_prefix('/etc/passwd', username .. ':', string.format('%s:*:%u:%u::/var:/bin/false\n', username, uid, gid))
+	util.replace_prefix('/etc/shadow', username .. ':', string.format('%s:*:0:0:99999:7:::\n', username))
+	util.unlock('/var/lock/passwd')
+end
+
+function remove_user(username)
+	util.lock('/var/lock/passwd')
+	util.replace_prefix('/etc/passwd', username .. ':')
+	util.replace_prefix('/etc/shadow', username .. ':')
+	util.unlock('/var/lock/passwd')
+end
+
+function add_group(groupname, gid)
+	util.lock('/var/lock/group')
+	util.replace_prefix('/etc/group', groupname .. ':', string.format('%s:x:%u:\n', groupname, gid))
+	util.unlock('/var/lock/group')
+end
+
+function remove_group(groupname)
+	util.lock('/var/lock/group')
+	util.replace_prefix('/etc/group', groupname .. ':')
+	util.unlock('/var/lock/group')
+end

+ 79 - 0
package/gluon-core/files/usr/lib/lua/gluon/util.lua

@@ -0,0 +1,79 @@
+-- Writes all lines from the file input to the file output except those starting with prefix
+-- Doesn't close the output file, but returns the file object
+local function do_filter_prefix(input, output, prefix)
+	local f = io.open(output, 'w+')
+	local l = prefix:len()
+
+	for line in io.lines(input) do
+		if line:sub(1, l) ~= prefix then
+			f:write(line, '\n')
+		end
+	end
+
+	return f
+end
+
+
+local function escape_args(ret, arg0, ...)
+	if not arg0 then
+		return ret
+	end
+
+	return escape_args(ret .. "'" .. string.gsub(arg0, "'", "'\\''") .. "' ", ...)
+end
+
+
+local os = os
+local string = string
+local tonumber = tonumber
+
+local nixio = require 'nixio'
+local sysconfig = require 'gluon.sysconfig'
+
+
+module 'gluon.util'
+
+function exec(...)
+	return os.execute(escape_args('', ...))
+end
+
+-- Removes all lines starting with a prefix from a file, optionally adding a new one
+function replace_prefix(file, prefix, add)
+	local tmp = file .. '.tmp'
+	local f = do_filter_prefix(file, tmp, prefix)
+	if add then
+		f:write(add)
+	end
+	f:close()
+	os.rename(tmp, file)
+end
+
+function lock(file)
+	exec('lock', file)
+end
+
+function unlock(file)
+	exec('lock', '-u', file)
+end
+
+function node_id()
+  return string.gsub(sysconfig.primary_mac, ':', '')
+end
+
+-- Generates a (hopefully) unique MAC address
+-- The first parameter defines the function and the second
+-- parameter an ID to add to the MAC address
+-- Functions and IDs defined so far:
+-- (1, 0): WAN (for mesh-on-WAN)
+-- (1, 1): LAN (for mesh-on-LAN)
+-- (2, n): client interface for the n'th radio
+-- (3, n): adhoc interface for n'th radio
+-- (4, 0): mesh VPN
+function generate_mac(f, i)
+  local m1, m2, m3, m4, m5, m6 = string.match(sysconfig.primary_mac, '(%x%x):(%x%x):(%x%x):(%x%x):(%x%x):(%x%x)')
+  m1 = nixio.bit.bor(tonumber(m1, 16), 0x02)
+  m2 = (tonumber(m2, 16)+f) % 0x100
+  m3 = (tonumber(m3, 16)+i) % 0x100
+
+  return string.format('%02x:%02x:%02x:%s:%s:%s', m1, m2, m3, m4, m5, m6)
+end

+ 40 - 0
package/gluon-cron/Makefile

@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-cron
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-cron
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Cron support
+  DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-cron/description
+	Gluon community wifi mesh firmware framework: cron support
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+	CFLAGS="$(TARGET_CFLAGS)" CPPFLAGS="$(TARGET_CPPFLAGS)" $(MAKE) -C $(PKG_BUILD_DIR) $(TARGET_CONFIGURE_OPTS)
+endef
+
+define Package/gluon-cron/install
+	$(CP) ./files/* $(1)/
+	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/gluon-crond $(1)/usr/sbin/
+endef
+
+$(eval $(call BuildPackage,gluon-cron))

+ 18 - 0
package/gluon-cron/files/etc/init.d/gluon-cron

@@ -0,0 +1,18 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2013 Project Gluon
+
+START=50
+
+SERVICE_USE_PID=1
+SERVICE_WRITE_PID=1
+SERVICE_DAEMONIZE=1
+
+CRONDIR=/lib/gluon/cron
+
+start () {
+	service_start /usr/sbin/gluon-crond "$CRONDIR"
+}
+
+stop() {
+	service_stop /usr/sbin/gluon-crond
+}

+ 0 - 0
package/gluon-cron/files/lib/gluon/cron/.keep


+ 3 - 0
package/gluon-cron/src/Makefile

@@ -0,0 +1,3 @@
+all: gluon-crond
+
+gluon-crond: gluon-crond.c

+ 316 - 0
package/gluon-cron/src/gluon-crond.c

@@ -0,0 +1,316 @@
+/*
+  Copyright (c) 2013, 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 <sys/types.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+
+typedef struct job {
+	struct job *next;
+
+	uint64_t minutes;
+	uint32_t hours;
+	uint32_t doms;
+	uint16_t months;
+	uint8_t dows;
+
+	char *command;
+} job_t;
+
+
+static const char const *const MONTHS[12] = {
+	"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"
+};
+
+static const char const *const WEEKDAYS[7] = {
+	"sun", "mon", "tue", "wed", "thu", "fri", "sat"
+};
+
+
+static const char *crondir;
+
+static job_t *jobs = NULL;
+
+
+static void usage(void) {
+	fprintf(stderr, "Usage: gluon-crond <crondir>\n");
+}
+
+
+static inline uint64_t bit(unsigned b) {
+	return ((uint64_t)1) << b;
+}
+
+static int strict_atoi(const char *s) {
+	char *end;
+	int ret = strtol(s, &end, 10);
+
+	if (*end)
+		return -1;
+	else
+		return ret;
+}
+
+static uint64_t parse_strings(const char *input, const char *const *strings, size_t n) {
+	size_t i;
+	for (i = 0; i < n; i++) {
+		if (strcasecmp(input, strings[i]) == 0)
+			return bit(i);
+	}
+
+	return 0;
+}
+
+static uint64_t parse_times(char *input, int min, int n) {
+	uint64_t ret = 0;
+	int step = 1;
+
+	char *comma = strchr(input, ',');
+	if (comma) {
+		*comma = 0;
+		ret = parse_times(comma+1, min, n);
+
+		if (!ret)
+			return 0;
+	}
+
+	char *slash = strchr(input, '/');
+	if (slash) {
+		*slash = 0;
+		step = strict_atoi(slash+1);
+
+		if (step <= 0)
+			return 0;
+	}
+
+	int begin, end;
+	char *minus = strchr(input, '-');
+	if (minus) {
+		*minus = 0;
+		begin = strict_atoi(input);
+		end = strict_atoi(minus+1);
+	}
+	else if (strcmp(input, "*") == 0) {
+		begin = min;
+		end = min+n-1;
+	}
+	else {
+		begin = end = strict_atoi(input);
+	}
+
+	if (begin < min || end < min)
+		return 0;
+
+	int i;
+	for (i = begin-min; i <= end-min; i += step)
+		ret |= bit(i % n);
+
+	return ret;
+}
+
+static int handle_line(const char *line) {
+	job_t job = {};
+	int ret = -1;
+	char *columns[5];
+	int i;
+	int len;
+
+	int matches = sscanf(line, "%ms %ms %ms %ms %ms %n", &columns[0], &columns[1], &columns[2], &columns[3], &columns[4], &len);
+	if (matches != 5 && matches != 6) {
+		if (matches <= 0)
+			ret = 0;
+
+		goto end;
+	}
+
+	job.minutes = parse_times(columns[0], 0, 60);
+	if (!job.minutes)
+		goto end;
+
+	job.hours = parse_times(columns[1], 0, 24);
+	if (!job.hours)
+		goto end;
+
+	job.doms = parse_times(columns[2], 1, 31);
+	if (!job.doms)
+		goto end;
+
+
+	job.months = parse_strings(columns[3], MONTHS, 12);
+
+	if (!job.months)
+		job.months = parse_times(columns[3], 1, 12);
+	if (!job.months)
+		goto end;
+
+	job.dows = parse_strings(columns[4], WEEKDAYS, 7);
+	if (!job.dows)
+		job.dows = parse_times(columns[4], 0, 7);
+	if (!job.dows)
+		goto end;
+
+	job.command = strdup(line+len);
+
+	job_t *jobp = malloc(sizeof(job_t));
+	*jobp = job;
+
+	jobp->next = jobs;
+	jobs = jobp;
+
+	ret = 0;
+
+  end:
+	for (i = 0; i < matches && i < 5; i++)
+		free(columns[i]);
+
+	return ret;
+}
+
+
+static void read_crontab(const char *name) {
+	FILE *file = fopen(name, "r");
+	if (!file) {
+		syslog(LOG_WARNING, "unable to read crontab `%s'", name);
+		return;
+	}
+
+	char line[16384];
+	unsigned lineno = 0;
+
+	while (fgets(line, sizeof(line), file)) {
+		lineno++;
+
+		char *comment = strchr(line, '#');
+		if (comment)
+			*comment = 0;
+
+		if (handle_line(line))
+			syslog(LOG_WARNING, "syntax error in `%s', line %u", name, lineno);
+	}
+
+	fclose(file);
+}
+
+
+static void read_crondir(void) {
+	DIR *dir;
+
+	if (chdir(crondir) || ((dir = opendir(".")) == NULL)) {
+		fprintf(stderr, "Unable to read crondir `%s'\n", crondir);
+		usage();
+		exit(1);
+	}
+
+	struct dirent *ent;
+	while ((ent = readdir(dir)) != NULL) {
+		if (ent->d_name[0] == '.')
+			continue;
+
+		read_crontab(ent->d_name);
+	}
+
+	closedir(dir);
+}
+
+
+static void run_job(const job_t *job) {
+	pid_t pid = fork();
+	if (pid == 0) {
+		execl("/bin/sh", "/bin/sh", "-c", job->command, (char*)NULL);
+		syslog(LOG_ERR, "unable to run job: exec failed");
+		_exit(1);
+	}
+	else if (pid < 0) {
+		syslog(LOG_ERR, "unable to run job: fork failed");
+	}
+}
+
+
+static void check_job(const job_t *job, const struct tm *tm) {
+	if (!(job->minutes & bit(tm->tm_min)))
+		return;
+
+	if (!(job->hours & bit(tm->tm_hour)))
+		return;
+
+	if (!(job->doms & bit(tm->tm_mday-1)))
+		return;
+
+	if (!(job->months & bit(tm->tm_mon)))
+		return;
+
+	if (!(job->dows & bit(tm->tm_wday)))
+		return;
+
+	run_job(job);
+}
+
+
+int main(int argc, char *argv[]) {
+	if (argc != 2) {
+		usage();
+
+		exit(argc < 2 ? 0 : 1);
+	}
+
+	crondir = argv[1];
+
+	signal(SIGCHLD, SIG_IGN);
+
+	read_crondir();
+
+	time_t t = time(NULL);
+	struct tm *tm = localtime(&t);
+	int minute = tm->tm_min;
+
+	while (1) {
+		sleep(60 - t%60);
+
+		t = time(NULL);
+		tm = localtime(&t);
+
+		minute = (minute+1)%60;
+		if (tm->tm_min != minute) {
+			/* clock has moved, don't execute jobs */
+			minute = tm->tm_min;
+			continue;
+		}
+
+		job_t *job;
+		for (job = jobs; job; job = job->next)
+			check_job(job, tm);
+	}
+}

+ 40 - 0
package/gluon-ebtables-filter-multicast/Makefile

@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-ebtables-filter-multicast
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-ebtables-filter-multicast
+  SECTION:=gluon
+  CATEGORY:=Gluon
+  TITLE:=Ebtables filters for multicast packets
+  DEPENDS:=+gluon-core +gluon-ebtables
+endef
+
+define Package/gluon-ebtables-filter-multicast/description
+	Gluon community wifi mesh firmware framework: Ebtables filters for multicast packets
+
+	These filters drop non-essential multicast traffic before it enters the mesh.
+
+	Allowed protocols are: DHCP, DHCPv6, ARP, ICMP, ICMPv6, BitTorrent local peer discovery, BABEL and OSPF
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-ebtables-filter-multicast/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-ebtables-filter-multicast))

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain

@@ -0,0 +1 @@
+chain('MULTICAST_OUT', 'DROP')

+ 3 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-arp

@@ -0,0 +1,3 @@
+rule 'MULTICAST_OUT -p ARP --arp-opcode Reply --arp-ip-src 0.0.0.0 -j DROP'
+rule 'MULTICAST_OUT -p ARP --arp-opcode Request --arp-ip-dst 0.0.0.0 -j DROP'
+rule 'MULTICAST_OUT -p ARP -j RETURN'

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-babel

@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol udp --ip6-destination-port 6696 -j RETURN'

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-btlpd

@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-destination 239.192.152.143 --ip-protocol udp --ip-destination-port 6771 -j RETURN'

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv4

@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol udp --ip-destination-port 67 -j RETURN'

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv6

@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol udp --ip6-destination-port 547 -j RETURN'

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmp

@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol icmp -j RETURN'

+ 2 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmpv6

@@ -0,0 +1,2 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol 0 -j RETURN' -- hop-by-hop
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol ipv6-icmp -j RETURN'

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-igmp

@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol igmp -j RETURN'

+ 2 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ospf

@@ -0,0 +1,2 @@
+rule 'MULTICAST_OUT -p IPv4 --ip-protocol ospf -j RETURN'
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol ospf -j RETURN'

+ 1 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ripng

@@ -0,0 +1 @@
+rule 'MULTICAST_OUT -p IPv6 --ip6-protocol udp --ip6-destination ff02::9 --ip6-destination-port 521 -j RETURN'

+ 2 - 0
package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/300-mcast

@@ -0,0 +1,2 @@
+rule 'FORWARD --logical-out br-client -o bat0 -d Multicast -j MULTICAST_OUT'
+rule 'OUTPUT --logical-out br-client -o bat0 -d Multicast -j MULTICAST_OUT'

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików