]]
+
+ return function ()
+ luci.template.render_string(msg, { pubkey=pubkey
+ , hostname=hostname
+ , site=site
+ , sysconfig=sysconfig
+ })
+ end
+end
diff --git a/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua b/package/gluon-config-mode-mesh-vpn/files/lib/gluon/config-mode/wizard/0300-mesh-vpn.lua
new file mode 100644
index 00000000..669a7bc3
--- /dev/null
+++ b/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
diff --git a/package/gluon-config-mode-mesh-vpn/i18n/de.po b/package/gluon-config-mode-mesh-vpn/i18n/de.po
new file mode 100644
index 00000000..8c613aa0
--- /dev/null
+++ b/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 \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."
diff --git a/package/gluon-config-mode-mesh-vpn/i18n/gluon-config-mode-mesh-vpn.pot b/package/gluon-config-mode-mesh-vpn/i18n/gluon-config-mode-mesh-vpn.pot
new file mode 100644
index 00000000..52e2eef8
--- /dev/null
+++ b/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 ""
diff --git a/package/gluon-core/Makefile b/package/gluon-core/Makefile
new file mode 100644
index 00000000..2c632a00
--- /dev/null
+++ b/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))
diff --git a/package/gluon-core/check_site.lua b/package/gluon-core/check_site.lua
new file mode 100644
index 00000000..1a6987a0
--- /dev/null
+++ b/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+$')
diff --git a/package/gluon-core/files/etc/uci-defaults/zzz-gluon-upgrade b/package/gluon-core/files/etc/uci-defaults/zzz-gluon-upgrade
new file mode 100755
index 00000000..a12ce78e
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/lib/gluon/core/sysconfig/.keep b/package/gluon-core/files/lib/gluon/core/sysconfig/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/package/gluon-core/files/lib/gluon/upgrade/001-upgrade b/package/gluon-core/files/lib/gluon/upgrade/001-upgrade
new file mode 100755
index 00000000..6caba148
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/lib/gluon/upgrade/010-primary-mac b/package/gluon-core/files/lib/gluon/upgrade/010-primary-mac
new file mode 100755
index 00000000..70aee39d
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/lib/gluon/upgrade/020-interfaces b/package/gluon-core/files/lib/gluon/upgrade/020-interfaces
new file mode 100755
index 00000000..a051c738
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/lib/gluon/upgrade/030-system b/package/gluon-core/files/lib/gluon/upgrade/030-system
new file mode 100755
index 00000000..d7a66605
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/lib/gluon/upgrade/100-dnsmasq b/package/gluon-core/files/lib/gluon/upgrade/100-dnsmasq
new file mode 100755
index 00000000..3636fbf6
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/lib/gluon/upgrade/110-network b/package/gluon-core/files/lib/gluon/upgrade/110-network
new file mode 100755
index 00000000..1fd78f3c
--- /dev/null
+++ b/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)
diff --git a/package/gluon-core/files/lib/gluon/upgrade/120-ntp-servers b/package/gluon-core/files/lib/gluon/upgrade/120-ntp-servers
new file mode 100755
index 00000000..2b3a2df6
--- /dev/null
+++ b/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')
diff --git a/package/gluon-core/files/lib/gluon/upgrade/130-reboot-on-oom b/package/gluon-core/files/lib/gluon/upgrade/130-reboot-on-oom
new file mode 100755
index 00000000..48cfc5a8
--- /dev/null
+++ b/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)
diff --git a/package/gluon-core/files/lib/gluon/upgrade/140-firewall-rules b/package/gluon-core/files/lib/gluon/upgrade/140-firewall-rules
new file mode 100755
index 00000000..792e06a2
--- /dev/null
+++ b/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')
diff --git a/package/gluon-core/files/lib/gluon/upgrade/200-wireless b/package/gluon-core/files/lib/gluon/upgrade/200-wireless
new file mode 100755
index 00000000..219e505d
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/lib/gluon/upgrade/999-version b/package/gluon-core/files/lib/gluon/upgrade/999-version
new file mode 100755
index 00000000..62f08206
--- /dev/null
+++ b/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'))
diff --git a/package/gluon-core/files/lib/upgrade/keep.d/gluon b/package/gluon-core/files/lib/upgrade/keep.d/gluon
new file mode 100644
index 00000000..bc82c775
--- /dev/null
+++ b/package/gluon-core/files/lib/upgrade/keep.d/gluon
@@ -0,0 +1 @@
+/lib/gluon/core/sysconfig/
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/platform.lua b/package/gluon-core/files/usr/lib/lua/gluon/platform.lua
new file mode 100644
index 00000000..3d56f081
--- /dev/null
+++ b/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
+
+
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/site_config.lua b/package/gluon-core/files/usr/lib/lua/gluon/site_config.lua
new file mode 100644
index 00000000..cf151483
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/sysconfig.lua b/package/gluon-core/files/usr/lib/lua/gluon/sysconfig.lua
new file mode 100644
index 00000000..ff61f05b
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/sysctl.lua b/package/gluon-core/files/usr/lib/lua/gluon/sysctl.lua
new file mode 100644
index 00000000..44b0c217
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/users.lua b/package/gluon-core/files/usr/lib/lua/gluon/users.lua
new file mode 100644
index 00000000..8e618d88
--- /dev/null
+++ b/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
diff --git a/package/gluon-core/files/usr/lib/lua/gluon/util.lua b/package/gluon-core/files/usr/lib/lua/gluon/util.lua
new file mode 100644
index 00000000..cf3677cb
--- /dev/null
+++ b/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
diff --git a/package/gluon-cron/Makefile b/package/gluon-cron/Makefile
new file mode 100644
index 00000000..ac92a92d
--- /dev/null
+++ b/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))
diff --git a/package/gluon-cron/files/etc/init.d/gluon-cron b/package/gluon-cron/files/etc/init.d/gluon-cron
new file mode 100755
index 00000000..27a05e1d
--- /dev/null
+++ b/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
+}
diff --git a/package/gluon-cron/files/lib/gluon/cron/.keep b/package/gluon-cron/files/lib/gluon/cron/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/package/gluon-cron/src/Makefile b/package/gluon-cron/src/Makefile
new file mode 100644
index 00000000..3f4c7a50
--- /dev/null
+++ b/package/gluon-cron/src/Makefile
@@ -0,0 +1,3 @@
+all: gluon-crond
+
+gluon-crond: gluon-crond.c
diff --git a/package/gluon-cron/src/gluon-crond.c b/package/gluon-cron/src/gluon-crond.c
new file mode 100644
index 00000000..11a87c42
--- /dev/null
+++ b/package/gluon-cron/src/gluon-crond.c
@@ -0,0 +1,316 @@
+/*
+ Copyright (c) 2013, Matthias Schiffer
+ 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+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 \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);
+ }
+}
diff --git a/package/gluon-ebtables-filter-multicast/Makefile b/package/gluon-ebtables-filter-multicast/Makefile
new file mode 100644
index 00000000..93b7f9a5
--- /dev/null
+++ b/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))
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain
new file mode 100644
index 00000000..ec0013a3
--- /dev/null
+++ b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/100-mcast-chain
@@ -0,0 +1 @@
+chain('MULTICAST_OUT', 'DROP')
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-arp b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-arp
new file mode 100644
index 00000000..8af1900a
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-babel b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-babel
new file mode 100644
index 00000000..d5b81771
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-btlpd b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-btlpd
new file mode 100644
index 00000000..20b709f8
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv4 b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv4
new file mode 100644
index 00000000..2fca2223
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv6 b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-dhcpv6
new file mode 100644
index 00000000..6d7f0f55
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmp b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmp
new file mode 100644
index 00000000..25a95f39
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmpv6 b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-icmpv6
new file mode 100644
index 00000000..a7b67414
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-igmp b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-igmp
new file mode 100644
index 00000000..2d3814ae
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ospf b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ospf
new file mode 100644
index 00000000..da928d4b
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ripng b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/110-mcast-allow-ripng
new file mode 100644
index 00000000..37d31877
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/300-mcast b/package/gluon-ebtables-filter-multicast/files/lib/gluon/ebtables/300-mcast
new file mode 100644
index 00000000..c52f122f
--- /dev/null
+++ b/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'
diff --git a/package/gluon-ebtables-filter-ra-dhcp/Makefile b/package/gluon-ebtables-filter-ra-dhcp/Makefile
new file mode 100644
index 00000000..ea6a737d
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/Makefile
@@ -0,0 +1,39 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-ebtables-filter-ra-dhcp
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-ebtables-filter-ra-dhcp
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Ebtables filters for Router Advertisement and DHCP packets
+ DEPENDS:=+gluon-core +gluon-ebtables
+endef
+
+define Package/gluon-ebtables-filter-ra-dhcp/description
+ Gluon community wifi mesh firmware framework: Ebtables filters for Router Advertisement and DHCP packets
+
+ These filters ensure that RA and DHCP packets are only forwarded from the mesh into the
+ client network, and not vice-versa.
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-ebtables-filter-ra-dhcp/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-ebtables-filter-ra-dhcp))
diff --git a/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv4 b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv4
new file mode 100644
index 00000000..ec56ff1d
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv4
@@ -0,0 +1,5 @@
+rule 'FORWARD -p IPv4 --ip-protocol udp --ip-destination-port 67 -j OUT_ONLY'
+rule 'OUTPUT -p IPv4 --ip-protocol udp --ip-destination-port 67 -j OUT_ONLY'
+
+rule 'FORWARD -p IPv4 --ip-protocol udp --ip-destination-port 68 -j IN_ONLY'
+rule 'INPUT -p IPv4 --ip-protocol udp --ip-destination-port 68 -j IN_ONLY'
diff --git a/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv6 b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv6
new file mode 100644
index 00000000..470a7648
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-dhcpv6
@@ -0,0 +1,5 @@
+rule 'FORWARD -p IPv6 --ip6-protocol udp --ip6-destination-port 547 -j OUT_ONLY'
+rule 'OUTPUT -p IPv6 --ip6-protocol udp --ip6-destination-port 547 -j OUT_ONLY'
+
+rule 'FORWARD -p IPv6 --ip6-protocol udp --ip6-destination-port 546 -j IN_ONLY'
+rule 'INPUT -p IPv6 --ip6-protocol udp --ip6-destination-port 546 -j IN_ONLY'
diff --git a/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-radv b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-radv
new file mode 100644
index 00000000..b34d4c76
--- /dev/null
+++ b/package/gluon-ebtables-filter-ra-dhcp/files/lib/gluon/ebtables/200-dir-radv
@@ -0,0 +1,5 @@
+rule 'FORWARD -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-solicitation -j OUT_ONLY'
+rule 'OUTPUT -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-solicitation -j OUT_ONLY'
+
+rule 'FORWARD -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-advertisement -j IN_ONLY'
+rule 'INPUT -p IPv6 --ip6-protocol ipv6-icmp --ip6-icmp-type router-advertisement -j IN_ONLY'
diff --git a/package/gluon-ebtables/Makefile b/package/gluon-ebtables/Makefile
new file mode 100644
index 00000000..39c654c1
--- /dev/null
+++ b/package/gluon-ebtables/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-ebtables
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-ebtables
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Ebtables support
+ DEPENDS:=+gluon-core +ebtables +kmod-ebtables-ipv4 +kmod-ebtables-ipv6 +kmod-ipt-core
+endef
+
+define Package/gluon-ebtables/description
+ Gluon community wifi mesh firmware framework: ebtables support
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-ebtables/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-ebtables))
diff --git a/package/gluon-ebtables/files/etc/init.d/gluon-ebtables b/package/gluon-ebtables/files/etc/init.d/gluon-ebtables
new file mode 100755
index 00000000..5a770452
--- /dev/null
+++ b/package/gluon-ebtables/files/etc/init.d/gluon-ebtables
@@ -0,0 +1,73 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2013 Project Gluon
+#
+# Firewall script for inserting and removing ebtables rules.
+#
+# Example format, for filtering any IPv4 multicast packets to the SSDP UDP port:
+# rule FORWARD --logical-out br-client -d Multicast -p IPv4 --ip-protocol udp --ip-destination-port 5355 -j DROP
+#
+# Removing all rules:
+# $ ./firewall-ebtables stop
+# Inserting all rules:
+# $ ./firewall-ebtables start
+# Inserting a specific rule file:
+# $ ./firewall-ebtables start /lib/gluon/ebtables/100-mcast-chain
+# Removing a specific rule file:
+# $ ./firewall-ebtables stop /lib/gluon/ebtables/100-mcast-chain
+
+
+START=19
+STOP=91
+
+
+exec_file() {
+ local file="$1"
+
+ /usr/bin/lua -e "
+ function rule(command)
+ os.execute($EBTABLES_RULE)
+ end
+ function chain(name, policy)
+ os.execute($EBTABLES_CHAIN)
+ end
+ " "$file"
+}
+
+exec_all() {
+ local sort_arg="$1"
+
+ local old_ifs="$IFS"
+ IFS='
+'
+ for file in `find /lib/gluon/ebtables -type f | sort $sort_arg`; do
+ exec_file "$file"
+ done
+ IFS="$old_ifs"
+}
+
+
+start() {
+ (
+ export EBTABLES_RULE='"ebtables -A " .. command'
+ export EBTABLES_CHAIN='"ebtables -N " .. name .. " -P " .. policy'
+
+ if [ -z "$1" ]; then
+ exec_all ''
+ else
+ exec_file "$1"
+ fi
+ )
+}
+
+stop() {
+ (
+ export EBTABLES_RULE='"ebtables -D " .. command'
+ export EBTABLES_CHAIN='"ebtables -X " .. name'
+
+ if [ -z "$1" ]; then
+ exec_all '-r'
+ else
+ exec_file "$1"
+ fi
+ )
+}
diff --git a/package/gluon-ebtables/files/lib/gluon/ebtables/100-dir-chain b/package/gluon-ebtables/files/lib/gluon/ebtables/100-dir-chain
new file mode 100644
index 00000000..31c19c53
--- /dev/null
+++ b/package/gluon-ebtables/files/lib/gluon/ebtables/100-dir-chain
@@ -0,0 +1,2 @@
+chain('IN_ONLY', 'RETURN')
+chain('OUT_ONLY', 'RETURN')
diff --git a/package/gluon-ebtables/files/lib/gluon/ebtables/101-dir-rules b/package/gluon-ebtables/files/lib/gluon/ebtables/101-dir-rules
new file mode 100644
index 00000000..b1cd4e24
--- /dev/null
+++ b/package/gluon-ebtables/files/lib/gluon/ebtables/101-dir-rules
@@ -0,0 +1,2 @@
+rule 'IN_ONLY --logical-in br-client -i ! bat0 -j DROP'
+rule 'OUT_ONLY --logical-out br-client -o ! bat0 -j DROP'
diff --git a/package/gluon-legacy/Makefile b/package/gluon-legacy/Makefile
new file mode 100644
index 00000000..7320fba6
--- /dev/null
+++ b/package/gluon-legacy/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-legacy
+PKG_VERSION:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-legacy
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Legacy update scripts
+ DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-legacy/description
+ Gluon community wifi mesh firmware framework: legacy update scripts
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-legacy/install
+ $(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-legacy/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-legacy))
diff --git a/package/gluon-legacy/check_site.lua b/package/gluon-legacy/check_site.lua
new file mode 100644
index 00000000..1ec26de0
--- /dev/null
+++ b/package/gluon-legacy/check_site.lua
@@ -0,0 +1,8 @@
+need_string_array 'legacy.version_files'
+need_string_array 'legacy.old_files'
+
+need_string_array 'legacy.config_mode_configs'
+need_string_array 'legacy.fastd_configs'
+need_string 'legacy.mesh_ifname'
+need_string_array 'legacy.tc_configs'
+need_string_array 'legacy.wifi_names'
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/000-legacy b/package/gluon-legacy/files/lib/gluon/upgrade/000-legacy
new file mode 100755
index 00000000..78436676
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/000-legacy
@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+for _, file in ipairs(site.legacy.version_files) do
+ if os.remove(file) then
+ -- Set version being upgraded from to 'legacy'
+ sysconfig.gluon_version = 'legacy'
+ end
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/019-legacy-interfaces b/package/gluon-legacy/files/lib/gluon/upgrade/019-legacy-interfaces
new file mode 100755
index 00000000..b48e42b8
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/019-legacy-interfaces
@@ -0,0 +1,40 @@
+#!/usr/bin/lua
+
+local gluon_util = require 'gluon.util'
+local platform = require 'gluon.platform'
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+local util = require 'luci.util'
+
+
+if sysconfig.gluon_version == 'legacy' then
+ local function iface_exists(name)
+ return (gluon_util.exec('ip', 'link', 'show', 'dev', (name:gsub('%..*$', ''))) == 0)
+ end
+
+ local function remove_bat0(iface)
+ return util.trim(string.gsub(' ' .. iface .. ' ', ' bat0 ', ' '))
+ end
+
+
+ local lan_ifname = remove_bat0(uci:get('network', site.legacy.mesh_ifname, 'ifname'))
+ local wan_ifname = uci:get('network', 'wan', 'ifname')
+
+ 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', site.legacy.mesh_ifname)
+ uci:delete('network', 'wan')
+
+ uci:save('network')
+ uci:commit('network')
+end
+
+
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/210-legacy-wireless b/package/gluon-legacy/files/lib/gluon/upgrade/210-legacy-wireless
new file mode 100755
index 00000000..36da0632
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/210-legacy-wireless
@@ -0,0 +1,24 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+ function delete_legacy_iface(iface)
+ for _, wifi in pairs(site.legacy.wifi_names) do
+ if wifi == iface['.name'] then
+ return true
+ end
+ end
+
+ return false
+ end
+
+ uci:delete_all('wireless', 'wifi-iface', delete_legacy_iface)
+
+ uci:save('wireless')
+ uci:commit('wireless')
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-setup-mode b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-setup-mode
new file mode 100755
index 00000000..0b97e120
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-setup-mode
@@ -0,0 +1,22 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+ for _, config in ipairs(site.legacy.config_mode_configs) do
+ local old = uci:get_first(config, 'wizard', 'configured')
+ if old == '1' then
+ local setup_mode = uci:get_first('gluon-setup-mode', 'setup_mode')
+ uci:set('gluon-setup-mode', setup_mode, 'configured', '1')
+
+ uci:save('gluon-setup-mode')
+ uci:commit('gluon-setup-mode')
+
+ break
+ end
+ end
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-simple-tc b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-simple-tc
new file mode 100755
index 00000000..c67afe14
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/290-legacy-simple-tc
@@ -0,0 +1,29 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+ for _, config in ipairs(site.legacy.tc_configs) do
+ local s = uci:get_first(config, 'bandwidth')
+ if s then
+ old = uci:get_all(config, s)
+ uci:section('gluon-simple-tc', 'interface', 'mesh_vpn',
+ {
+ ifname = 'mesh-vpn',
+ enabled = old.enabled,
+ limit_ingress = old.downstream,
+ limit_egress = old.upstream,
+ }
+ )
+
+ uci:save('gluon-simple-tc')
+ uci:commit('gluon-simple-tc')
+
+ break
+ end
+ end
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/390-legacy-mesh-vpn-fastd b/package/gluon-legacy/files/lib/gluon/upgrade/390-legacy-mesh-vpn-fastd
new file mode 100755
index 00000000..468a35a6
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/390-legacy-mesh-vpn-fastd
@@ -0,0 +1,37 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+local uci = require('luci.model.uci').cursor()
+
+
+if sysconfig.gluon_version == 'legacy' then
+ local secret
+ local enabled
+
+
+ for _, config in ipairs(site.legacy.fastd_configs) do
+ if not secret then
+ local s = uci:get_all('fastd', config)
+ if s then
+ secret = s.secret
+ enabled = s.enabled
+ end
+ end
+
+ uci:delete('fastd', config)
+ end
+
+ if secret then
+ uci:section('fastd', 'fastd', 'mesh_vpn',
+ {
+ secret = secret,
+ enabled = enabled,
+ }
+ )
+ end
+
+ uci:save('fastd')
+ uci:commit('fastd')
+end
diff --git a/package/gluon-legacy/files/lib/gluon/upgrade/990-legacy-late b/package/gluon-legacy/files/lib/gluon/upgrade/990-legacy-late
new file mode 100755
index 00000000..efb6b675
--- /dev/null
+++ b/package/gluon-legacy/files/lib/gluon/upgrade/990-legacy-late
@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site_config'
+local sysconfig = require 'gluon.sysconfig'
+
+
+if sysconfig.gluon_version == 'legacy' then
+ for _, file in ipairs(site.legacy.old_files) do
+ os.remove(file)
+ end
+end
diff --git a/package/gluon-lock-password/Makefile b/package/gluon-lock-password/Makefile
new file mode 100644
index 00000000..d0e99373
--- /dev/null
+++ b/package/gluon-lock-password/Makefile
@@ -0,0 +1,36 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-lock-password
+PKG_VERSION:=1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-lock-password
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Locks the root account by default
+ DEPENDS:=+gluon-core
+endef
+
+define Package/gluon-lock-password/description
+ This packages locks the root account by default.
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-lock-password/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-lock-password))
diff --git a/package/gluon-lock-password/files/lib/gluon/upgrade/100-lock-password b/package/gluon-lock-password/files/lib/gluon/upgrade/100-lock-password
new file mode 100755
index 00000000..3204f638
--- /dev/null
+++ b/package/gluon-lock-password/files/lib/gluon/upgrade/100-lock-password
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+has_root_pwd() {
+ local pwd
+
+ pwd=$([ -f "$1" ] && cat "$1")
+ pwd="${pwd#*root:}"
+ pwd="${pwd%%:*}"
+
+ test -n "${pwd}"
+}
+
+has_root_pwd /etc/shadow || passwd -l root
diff --git a/package/gluon-luci-admin/Makefile b/package/gluon-luci-admin/Makefile
new file mode 100644
index 00000000..455bcb25
--- /dev/null
+++ b/package/gluon-luci-admin/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-admin
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-admin
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Luci based simple administration interface for mesh nodes
+ DEPENDS:=+gluon-config-mode-core
+endef
+
+define Package/gluon-luci-admin/description
+ Luci based config mode
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-admin/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-admin))
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/index.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/index.lua
new file mode 100644
index 00000000..284cc162
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/index.lua
@@ -0,0 +1,39 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth
+Copyright 2008 Jo-Philipp Wich
+
+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.admin.index", package.seeall)
+
+function index()
+ local uci_state = luci.model.uci.cursor_state()
+
+ -- Disable gluon-luci-admin when setup mode is not enabled
+ if uci_state:get_first('gluon-setup-mode', 'setup_mode', 'running', '0') ~= '1' then
+ return
+ end
+
+ local root = node()
+ if not root.lock then
+ root.target = alias("admin")
+ root.index = true
+ end
+
+ local page = entry({"admin"}, alias("admin", "index"), "Expert Mode", 10)
+ page.sysauth = "root"
+ page.sysauth_authenticator = function() return "root" end
+ page.index = true
+
+ entry({"admin", "index"}, cbi("admin/info"), _("Info"), 1).ignoreindex = true
+ entry({"admin", "remote"}, cbi("admin/remote"), _("Remotezugriff"), 10)
+end
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/upgrade.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/upgrade.lua
new file mode 100644
index 00000000..868c2bbd
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/controller/admin/upgrade.lua
@@ -0,0 +1,135 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth
+Copyright 2008 Jo-Philipp Wich
+
+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.admin.upgrade", package.seeall)
+
+function index()
+ local has_platform = nixio.fs.access("/lib/upgrade/platform.sh")
+ if has_platform then
+ entry({"admin", "upgrade"}, call("action_upgrade"), "Firmware aktualisieren", 90)
+ entry({"admin", "upgrade", "reboot"}, template("admin/upgrade_reboot"), nil, nil)
+ end
+end
+
+function action_upgrade()
+ local tmpfile = "/tmp/firmware.img"
+
+ -- Install upload handler
+ local file
+ luci.http.setfilehandler(
+ function(meta, chunk, eof)
+ if not nixio.fs.access(tmpfile) and not file and chunk and #chunk > 0 then
+ file = io.open(tmpfile, "w")
+ end
+ if file and chunk then
+ file:write(chunk)
+ end
+ if file and eof then
+ file:close()
+ end
+ end
+ )
+
+ -- Determine state
+ local step = tonumber(luci.http.formvalue("step") or 1)
+ local has_image = nixio.fs.access(tmpfile)
+ local has_support = image_supported(tmpfile)
+
+ -- Step 1: file upload, error on unsupported image format
+ if not has_image or not has_support or step == 1 then
+ -- If there is an image but user has requested step 1
+ -- or type is not supported, then remove it.
+ if has_image then
+ nixio.fs.unlink(tmpfile)
+ end
+
+ luci.template.render("admin/upgrade", {
+ bad_image=(has_image and not has_support or false)
+ } )
+
+ -- Step 2: present uploaded file, show checksum, confirmation
+ elseif step == 2 then
+ luci.template.render("admin/upgrade_confirm", {
+ checksum=image_checksum(tmpfile),
+ filesize=nixio.fs.stat(tmpfile).size,
+ flashsize=storage_size(),
+ keepconfig=luci.http.formvalue("keepcfg") == "1"
+ } )
+ elseif step == 3 then
+ local keepcfg = luci.http.formvalue("keepcfg") == "1"
+ fork_exec("/sbin/sysupgrade %s %q" % { keepcfg and "" or "-n", tmpfile })
+ luci.http.redirect(luci.dispatcher.build_url("admin", "upgrade", "reboot"))
+ end
+end
+
+function fork_exec(command)
+ local pid = nixio.fork()
+ if pid > 0 then
+ return
+ elseif pid == 0 then
+ -- change to root dir
+ nixio.chdir("/")
+
+ -- patch stdin, out, err to /dev/null
+ local null = nixio.open("/dev/null", "w+")
+ if null then
+ nixio.dup(null, nixio.stderr)
+ nixio.dup(null, nixio.stdout)
+ nixio.dup(null, nixio.stdin)
+ if null:fileno() > 2 then
+ null:close()
+ end
+ end
+
+ -- replace with target command
+ nixio.exec("/bin/sh", "-c", command)
+ end
+end
+
+function image_supported(tmpfile)
+ -- XXX: yay...
+ return ( 0 == os.execute(
+ ". /lib/functions.sh; " ..
+ "include /lib/upgrade; " ..
+ "platform_check_image %q >/dev/null"
+ % tmpfile
+ ) )
+end
+
+function storage_size()
+ local size = 0
+ if nixio.fs.access("/proc/mtd") then
+ for l in io.lines("/proc/mtd") do
+ local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
+ if n == "linux" then
+ size = tonumber(s, 16)
+ break
+ end
+ end
+ elseif nixio.fs.access("/proc/partitions") then
+ for l in io.lines("/proc/partitions") do
+ local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
+ if b and n and not n:match('[0-9]') then
+ size = tonumber(b) * 1024
+ break
+ end
+ end
+ end
+ return size
+end
+
+function image_checksum(tmpfile)
+ return (luci.sys.exec("md5sum %q" % tmpfile):match("^([^%s]+)"))
+end
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/info.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/info.lua
new file mode 100644
index 00000000..e9ceba70
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/info.lua
@@ -0,0 +1,4 @@
+local t = Template('admin/info')
+t.pageaction = false
+
+return t
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/remote.lua b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/remote.lua
new file mode 100644
index 00000000..dfba5e66
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/model/cbi/admin/remote.lua
@@ -0,0 +1,108 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2008 Steven Barth
+Copyright 2011 Jo-Philipp Wich
+Copyright 2013 Nils Schneider
+
+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$
+]]--
+
+local fs = require "nixio.fs"
+
+local m = Map("system", "SSH-Keys")
+m.submit = "Speichern"
+m.reset = "Zurücksetzen"
+m.pageaction = false
+m.template = "admin/expertmode"
+
+if fs.access("/etc/config/dropbear") then
+ local s = m:section(TypedSection, "_dummy1", nil,
+ "Hier hast du die Möglichkeit SSH-Keys (einen pro Zeile) zu hinterlegen:")
+
+ s.addremove = false
+ s.anonymous = true
+
+ function s.cfgsections()
+ return { "_keys" }
+ end
+
+ local keys
+
+ keys = s:option(TextValue, "_data", "")
+ keys.wrap = "off"
+ keys.rows = 5
+ keys.rmempty = true
+
+ function keys.cfgvalue()
+ return fs.readfile("/etc/dropbear/authorized_keys") or ""
+ end
+
+ function keys.write(self, section, value)
+ if value then
+ fs.writefile("/etc/dropbear/authorized_keys", value:gsub("\r\n", "\n"))
+ end
+ end
+
+ function keys.remove(self, section)
+ if keys:formvalue("_keys") then
+ fs.remove("/etc/dropbear/authorized_keys")
+ end
+ end
+end
+
+local m2 = Map("system", "Passwort")
+m2.submit = "Speichern"
+m2.reset = false
+m2.pageaction = false
+m2.template = "admin/expertmode"
+
+local s = m2:section(TypedSection, "_dummy2", nil,
+[[Alternativ kannst du auch ein Passwort setzen. Wähle bitte ein sicheres Passwort, das du nirgendwo anders verwendest.
+Beim Setzen eines leeren Passworts wird der Login per Passwort gesperrt (dies ist die Standard-Einstellung).]])
+
+s.addremove = false
+s.anonymous = true
+
+local pw1 = s:option(Value, "pw1", "Passwort")
+pw1.password = true
+
+local pw2 = s:option(Value, "pw2", "Wiederholung")
+pw2.password = true
+
+function s.cfgsections()
+ return { "_pass" }
+end
+
+function m2.on_commit(map)
+ local v1 = pw1:formvalue("_pass")
+ local v2 = pw2:formvalue("_pass")
+
+ if v1 and v2 then
+ if v1 == v2 then
+ if #v1 > 0 then
+ if luci.sys.user.setpasswd(luci.dispatcher.context.authuser, v1) == 0 then
+ m2.message = "Passwort geändert."
+ else
+ m2.errmessage = "Das Passwort konnte nicht geändert werden."
+ end
+ else
+ -- We don't check the return code here as the error 'password for root is already locked' is normal...
+ os.execute('passwd -l root >/dev/null')
+ m2.message = "Passwort gelöscht."
+ end
+ else
+ m2.errmessage = "Die beiden Passwörter stimmen nicht überein."
+ end
+ end
+end
+
+local c = Compound(m, m2)
+c.pageaction = false
+return c
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/expertmode.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/expertmode.htm
new file mode 100644
index 00000000..53947f3c
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/expertmode.htm
@@ -0,0 +1,56 @@
+<% if not self.embedded then %>
+
+<% end %>
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/info.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/info.htm
new file mode 100644
index 00000000..9c384399
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/info.htm
@@ -0,0 +1,44 @@
+<%-
+ local fs = require 'luci.fs'
+ local uci = require('luci.model.uci').cursor()
+ local util = require 'luci.util'
+
+ local site = require 'gluon.site_config'
+ local sysconfig = require 'gluon.sysconfig'
+ local platform = require 'gluon.platform'
+
+
+ local keys = {
+ hostname = 'Hostname',
+ primary_mac = 'MAC-Adresse',
+ model = 'Hardware-Modell',
+ version = 'Gluon-Version',
+ release = 'Firmware-Release',
+ site = 'Site',
+ pubkey = 'Öffentlicher VPN-Schlüssel',
+ }
+
+ local values = {
+ hostname = uci:get_first('system', 'system', 'hostname'),
+ primary_mac = sysconfig.primary_mac,
+ model = platform.get_model(),
+ version = util.trim(fs.readfile('/lib/gluon/gluon-version')),
+ release = util.trim(fs.readfile('/lib/gluon/release')),
+ site = site.site_name,
+ pubkey = 'n/a',
+ }
+
+ local meshvpn_enabled = uci:get("fastd", "mesh_vpn", "enabled", "0")
+ if meshvpn_enabled == "1" then
+ local pubkey = util.trim(util.exec('/etc/init.d/fastd show_key mesh_vpn'))
+ if pubkey ~= '' then
+ values.pubkey = pubkey
+ end
+ end
+-%>
+
Info
+<% for _, key in ipairs({'hostname', 'primary_mac', 'model', 'version', 'release', 'site', 'pubkey'}) do %>
+
+
<%=keys[key]%>
<%=values[key] or 'n/a'%>
+
+<% end %>
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade.htm
new file mode 100644
index 00000000..343ed495
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade.htm
@@ -0,0 +1,55 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008 Steven Barth
+Copyright 2008-2009 Jo-Philipp Wich
+
+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$
+
+-%>
+
+<%+header%>
+
+
Firmware aktualisieren
+
+
+<%+footer%>
+
diff --git a/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_confirm.htm b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_confirm.htm
new file mode 100644
index 00000000..ac375761
--- /dev/null
+++ b/package/gluon-luci-admin/files/usr/lib/lua/luci/view/admin/upgrade_confirm.htm
@@ -0,0 +1,67 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008 Steven Barth
+Copyright 2008-2009 Jo-Philipp Wich
+
+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$
+
+-%>
+
+<%+header%>
+
+
Firmware aktualisieren
+
+ Die Firmwaredatei wurde übermittelt. Bitte vergleiche MD5-Checksumme
+ und Dateigröße und klicke anschließend auf "Fortfahren".
+
+
+<% if flashsize > 0 and filesize > flashsize then %>
+
Die Firmware passt nicht in den Speicher des Gerätes.
+<% end %>
+
+
+
+
md5sum: <%=checksum%>
+
Größe: <%
+ function byte_format(byte)
+ local suff = {"B", "KB", "MB", "GB", "TB"}
+ for i=1, 5 do
+ if byte > 1024 and i < 5 then
+ byte = byte / 1024
+ else
+ return string.format("%.2f %s", byte, suff[i])
+ end
+ end
+ end
+
+ write(byte_format(filesize))
+
+ if flashsize > 0 then
+ write(luci.i18n.translatef(
+ " (%s available)",
+ w.byte_format(flashsize)
+ ))
+ end
+ %>
+ Die Firmware wird jetzt aktualisiert.
+ UNTERBRICH AUF KEINEN FALL DIE STROMVERSORGUNG!
+ Dieser Vorgang wird einige Minuten dauern.
+ Anschließend startet das Gerät automatisch neu.
+
+
+
+
+
diff --git a/package/gluon-luci-autoupdater/Makefile b/package/gluon-luci-autoupdater/Makefile
new file mode 100644
index 00000000..518bd6df
--- /dev/null
+++ b/package/gluon-luci-autoupdater/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-autoupdater
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-autoupdater
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Luci module for gluon-autoupdater
+ DEPENDS:=+gluon-luci-admin +gluon-autoupdater
+endef
+
+define Package/gluon-luci-autoupdater/description
+ Luci module for gluon-autoupdater
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-autoupdater/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-autoupdater))
diff --git a/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/controller/admin/autoupdater.lua b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/controller/admin/autoupdater.lua
new file mode 100644
index 00000000..9e6fd79f
--- /dev/null
+++ b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/controller/admin/autoupdater.lua
@@ -0,0 +1,20 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2013 Nils Schneider
+
+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.admin.autoupdater", package.seeall)
+
+function index()
+ entry({"admin", "autoupdater"}, cbi("admin/autoupdater"), _("Autoupdater"), 20)
+end
+
diff --git a/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/model/cbi/admin/autoupdater.lua b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/model/cbi/admin/autoupdater.lua
new file mode 100644
index 00000000..6800422b
--- /dev/null
+++ b/package/gluon-luci-autoupdater/files/usr/lib/lua/luci/model/cbi/admin/autoupdater.lua
@@ -0,0 +1,31 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2013 Nils Schneider
+
+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$
+]]--
+
+m = Map("autoupdater", "Autoupdater")
+m.submit = "Speichern"
+m.reset = "Zurücksetzen"
+m.pageaction = false
+m.template = "admin/expertmode"
+
+s = m:section(TypedSection, "autoupdater", nil)
+s.addremove = false
+s.anonymous = true
+
+s:option(Flag, "enabled", "Aktivieren")
+f = s:option(ListValue, "branch", "Branch")
+
+uci.cursor():foreach("autoupdater", "branch", function (section) f:value(section[".name"]) end)
+
+return m
+
diff --git a/package/gluon-luci-node-role/Makefile b/package/gluon-luci-node-role/Makefile
new file mode 100644
index 00000000..5e0fbb00
--- /dev/null
+++ b/package/gluon-luci-node-role/Makefile
@@ -0,0 +1,37 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-node-role
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/gluon-luci-node-role
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ DEPENDS:=+gluon-luci-admin +gluon-node-info
+ TITLE:=UI for specifying node role
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-node-role/install
+ $(CP) ./files/* $(1)/
+endef
+
+define Package/gluon-luci-node-role/postinst
+#!/bin/sh
+$(call GluonCheckSite,check_site.lua)
+endef
+
+$(eval $(call BuildPackage,gluon-luci-node-role))
diff --git a/package/gluon-luci-node-role/check_site.lua b/package/gluon-luci-node-role/check_site.lua
new file mode 100644
index 00000000..cd3ca937
--- /dev/null
+++ b/package/gluon-luci-node-role/check_site.lua
@@ -0,0 +1,8 @@
+local function check_role(k, _)
+ local role = string.format('roles.list[%q]', k)
+
+ need_string(role)
+end
+
+need_string('roles.default')
+need_table('roles.list', check_role)
diff --git a/package/gluon-luci-node-role/files/usr/lib/lua/luci/controller/admin/noderole.lua b/package/gluon-luci-node-role/files/usr/lib/lua/luci/controller/admin/noderole.lua
new file mode 100644
index 00000000..dcde01f1
--- /dev/null
+++ b/package/gluon-luci-node-role/files/usr/lib/lua/luci/controller/admin/noderole.lua
@@ -0,0 +1,5 @@
+module("luci.controller.admin.noderole", package.seeall)
+
+function index()
+ entry({"admin", "noderole"}, cbi("admin/noderole"), "Verwendungszweck", 20)
+end
diff --git a/package/gluon-luci-node-role/files/usr/lib/lua/luci/model/cbi/admin/noderole.lua b/package/gluon-luci-node-role/files/usr/lib/lua/luci/model/cbi/admin/noderole.lua
new file mode 100644
index 00000000..1d6d1798
--- /dev/null
+++ b/package/gluon-luci-node-role/files/usr/lib/lua/luci/model/cbi/admin/noderole.lua
@@ -0,0 +1,36 @@
+local f, s, o
+local site = require 'gluon.site_config'
+local uci = luci.model.uci.cursor()
+local config = 'gluon-node-info'
+
+-- where to read the configuration from
+local role = uci:get(config, uci:get_first(config, "system"), "role")
+
+f = SimpleForm("role", "Verwendungszweck")
+f.reset = false
+f.template = "admin/expertmode"
+f.submit = "Fertig"
+
+s = f:section(SimpleSection, nil, [[
+Wenn dein Freifunk-Router eine besondere Rolle im Freifunk Netz einnimmt, kannst du diese hier angeben.
+Bringe bitte zuvor in Erfahrung welche Auswirkungen die zur Verfügung stehenden Rollen im Freifunk-Netz haben.
+Setze die Rolle nur, wenn du weißt was du machst.
+]])
+
+o = s:option(ListValue, "role", "Rolle")
+o.default = role
+o.rmempty = false
+for role, prettyname in pairs(site.roles.list) do
+ o:value(role, prettyname)
+end
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ uci:set(config, uci:get_first(config, "system"), "role", data.role)
+
+ uci:save(config)
+ uci:commit(config)
+ end
+end
+
+return f
diff --git a/package/gluon-luci-portconfig/Makefile b/package/gluon-luci-portconfig/Makefile
new file mode 100644
index 00000000..92b3cbcf
--- /dev/null
+++ b/package/gluon-luci-portconfig/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-portconfig
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-portconfig
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Luci module for advanced ethernet port configuration
+ DEPENDS:=+gluon-luci-admin +gluon-mesh-batman-adv
+endef
+
+define Package/gluon-luci-portconfig/description
+ Luci module for advanced ethernet port configuration
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-portconfig/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-portconfig))
diff --git a/package/gluon-luci-portconfig/files/usr/lib/lua/luci/controller/admin/portconfig.lua b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/controller/admin/portconfig.lua
new file mode 100644
index 00000000..ff0e0af2
--- /dev/null
+++ b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/controller/admin/portconfig.lua
@@ -0,0 +1,20 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2013 Nils Schneider
+
+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.admin.portconfig", package.seeall)
+
+function index()
+ entry({"admin", "portconfig"}, cbi("admin/portconfig"), _("Schnittstellen"), 20)
+end
+
diff --git a/package/gluon-luci-portconfig/files/usr/lib/lua/luci/model/cbi/admin/portconfig.lua b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/model/cbi/admin/portconfig.lua
new file mode 100644
index 00000000..7233d5c9
--- /dev/null
+++ b/package/gluon-luci-portconfig/files/usr/lib/lua/luci/model/cbi/admin/portconfig.lua
@@ -0,0 +1,133 @@
+--[[
+LuCI - Lua Configuration Interface
+
+Copyright 2014 Nils Schneider
+
+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$
+]]--
+
+local uci = luci.model.uci.cursor()
+
+local wan = uci:get_all("network", "wan")
+local wan6 = uci:get_all("network", "wan6")
+local dns = uci:get_first("gluon-wan-dnsmasq", "static")
+
+local f = SimpleForm("portconfig", "WAN-Verbindung")
+f.template = "admin/expertmode"
+f.submit = "Speichern"
+f.reset = "Zurücksetzen"
+
+local s
+local o
+
+s = f:section(SimpleSection, nil, nil)
+
+o = s:option(ListValue, "ipv4", "IPv4")
+o:value("dhcp", "Automatisch (DHCP)")
+o:value("static", "Statisch")
+o:value("none", "Deaktiviert")
+o.default = wan.proto
+
+o = s:option(Value, "ipv4_addr", "IP-Adresse")
+o:depends("ipv4", "static")
+o.value = wan.ipaddr
+o.datatype = "ip4addr"
+o.rmempty = false
+
+o = s:option(Value, "ipv4_netmask", "Netzmaske")
+o:depends("ipv4", "static")
+o.value = wan.netmask or "255.255.255.0"
+o.datatype = "ip4addr"
+o.rmempty = false
+
+o = s:option(Value, "ipv4_gateway", "Gateway")
+o:depends("ipv4", "static")
+o.value = wan.gateway
+o.datatype = "ip4addr"
+o.rmempty = false
+
+
+s = f:section(SimpleSection, nil, nil)
+
+o = s:option(ListValue, "ipv6", "IPv6")
+o:value("dhcpv6", "Automatisch (RA/DHCPv6)")
+o:value("static", "Statisch")
+o:value("none", "Deaktiviert")
+o.default = wan6.proto
+
+o = s:option(Value, "ipv6_addr", "IP-Adresse")
+o:depends("ipv6", "static")
+o.value = wan6.ip6addr
+o.datatype = "ip6addr"
+o.rmempty = false
+
+o = s:option(Value, "ipv6_gateway", "Gateway")
+o:depends("ipv6", "static")
+o.value = wan6.ip6gw
+o.datatype = "ip6addr"
+o.rmempty = false
+
+
+if dns then
+ s = f:section(SimpleSection, nil, nil)
+
+ o = s:option(DynamicList, "dns", "Statische DNS-Server")
+ o:write(nil, uci:get("gluon-wan-dnsmasq", dns, "server"))
+ o.datatype = "ipaddr"
+end
+
+s = f:section(SimpleSection, nil, nil)
+
+o = s:option(Flag, "mesh_wan", "Mesh auf dem WAN-Port aktivieren")
+o.default = uci:get_bool("network", "mesh_wan", "auto") and o.enabled or o.disabled
+o.rmempty = false
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ uci:set("network", "wan", "proto", data.ipv4)
+ if data.ipv4 == "static" then
+ uci:set("network", "wan", "ipaddr", data.ipv4_addr)
+ uci:set("network", "wan", "netmask", data.ipv4_netmask)
+ uci:set("network", "wan", "gateway", data.ipv4_gateway)
+ else
+ uci:delete("network", "wan", "ipaddr")
+ uci:delete("network", "wan", "netmask")
+ uci:delete("network", "wan", "gateway")
+ end
+
+ uci:set("network", "wan6", "proto", data.ipv6)
+ if data.ipv6 == "static" then
+ uci:set("network", "wan6", "ip6addr", data.ipv6_addr)
+ uci:set("network", "wan6", "ip6gw", data.ipv6_gateway)
+ else
+ uci:delete("network", "wan6", "ip6addr")
+ uci:delete("network", "wan6", "ip6gw")
+ end
+
+ uci:set("network", "mesh_wan", "auto", data.mesh_wan)
+
+ uci:save("network")
+ uci:commit("network")
+
+ if dns then
+ if #data.dns > 0 then
+ uci:set("gluon-wan-dnsmasq", dns, "server", data.dns)
+ else
+ uci:delete("gluon-wan-dnsmasq", dns, "server")
+ end
+
+ uci:save("gluon-wan-dnsmasq")
+ uci:commit("gluon-wan-dnsmasq")
+ end
+ end
+
+ return true
+end
+
+return f
diff --git a/package/gluon-luci-private-wifi/Makefile b/package/gluon-luci-private-wifi/Makefile
new file mode 100644
index 00000000..0d5c4619
--- /dev/null
+++ b/package/gluon-luci-private-wifi/Makefile
@@ -0,0 +1,32 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-private-wifi
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-private-wifi
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ DEPENDS:=+gluon-luci-admin
+ TITLE:=UI for activating a private WLAN
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-private-wifi/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-private-wifi))
diff --git a/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/controller/admin/privatewifi.lua b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/controller/admin/privatewifi.lua
new file mode 100644
index 00000000..0b10b4e9
--- /dev/null
+++ b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/controller/admin/privatewifi.lua
@@ -0,0 +1,5 @@
+module("luci.controller.admin.privatewifi", package.seeall)
+
+function index()
+ entry({"admin", "privatewifi"}, cbi("admin/privatewifi"), "Privates WLAN", 10)
+end
diff --git a/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/model/cbi/admin/privatewifi.lua b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/model/cbi/admin/privatewifi.lua
new file mode 100644
index 00000000..b9157310
--- /dev/null
+++ b/package/gluon-luci-private-wifi/files/usr/lib/lua/luci/model/cbi/admin/privatewifi.lua
@@ -0,0 +1,63 @@
+local f, s, o, ssid
+local uci = luci.model.uci.cursor()
+local config = 'wireless'
+
+-- where to read the configuration from
+local primary_iface = 'wan_radio0'
+local ssid = uci:get(config, primary_iface, "ssid")
+
+f = SimpleForm("wifi", "Privates WLAN")
+f.reset = false
+f.template = "admin/expertmode"
+f.submit = "Fertig"
+
+s = f:section(SimpleSection, nil, [[
+Dein Freifunk-Router kann ebenfalls die Reichweite deines privaten Netzes erweitern.
+Hierfür wird der WAN-Port mit einem seperaten WLAN gebridged.
+Diese Funktionalität ist völlig unabhängig von Freifunk.
+Beachte, dass du nicht gleichzeitig das Meshen über den WAN Port aktiviert haben solltest.
+]])
+
+o = s:option(Flag, "enabled", "Aktiviert")
+o.default = (ssid and not uci:get_bool(config, primary_iface, "disabled")) and o.enabled or o.disabled
+o.rmempty = false
+
+o = s:option(Value, "ssid", "Name (SSID)")
+o.default = ssid
+
+o = s:option(Value, "key", "Schlüssel", "8-63 Zeichen")
+o.datatype = "wpakey"
+o.default = uci:get(config, primary_iface, "key")
+
+function f.handle(self, state, data)
+ if state == FORM_VALID then
+ uci:foreach(config, "wifi-device",
+ function(s)
+ local device = s['.name']
+ local name = "wan_" .. device
+
+ if data.enabled == '1' then
+ -- set up WAN wifi-iface
+ local t = uci:get_all(config, name) or {}
+
+ t.device = device
+ t.network = "wan"
+ t.mode = 'ap'
+ t.encryption = 'psk2'
+ t.ssid = data.ssid
+ t.key = data.key
+ t.disabled = "false"
+
+ uci:section(config, "wifi-iface", name, t)
+ else
+ -- disable WAN wifi-iface
+ uci:set(config, name, "disabled", "true")
+ end
+ end)
+
+ uci:save(config)
+ uci:commit(config)
+ end
+end
+
+return f
diff --git a/package/gluon-luci-theme/Makefile b/package/gluon-luci-theme/Makefile
new file mode 100644
index 00000000..4fc947c4
--- /dev/null
+++ b/package/gluon-luci-theme/Makefile
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 Nils Schneider
+# This is free software, licensed under the Apache 2.0 license.
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-luci-theme
+PKG_VERSION:=0.1
+PKG_RELEASE:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/gluon-luci-theme
+ SECTION:=gluon
+ CATEGORY:=Gluon
+ TITLE:=Luci theme for Gluon
+ DEPENDS:=
+endef
+
+define Package/gluon-luci-theme/description
+ Luci based config mode
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/gluon-luci-theme/install
+ $(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,gluon-luci-theme))
diff --git a/package/gluon-luci-theme/files/etc/uci-defaults/luci-theme-gluon b/package/gluon-luci-theme/files/etc/uci-defaults/luci-theme-gluon
new file mode 100755
index 00000000..795bd186
--- /dev/null
+++ b/package/gluon-luci-theme/files/etc/uci-defaults/luci-theme-gluon
@@ -0,0 +1,6 @@
+#!/bin/sh
+uci batch <<-EOF
+ set luci.themes.Gluon=/luci-static/gluon
+ commit luci
+EOF
+
diff --git a/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/footer.htm b/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/footer.htm
new file mode 100644
index 00000000..6b709030
--- /dev/null
+++ b/package/gluon-luci-theme/files/usr/lib/lua/luci/view/themes/gluon/footer.htm
@@ -0,0 +1,19 @@
+<%#
+LuCI - Lua Configuration Interface
+Copyright 2008 Steven Barth
+Copyright 2008 Jo-Philipp Wich
+
+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$
+
+-%>
+
+
+
+