This commit is contained in:
lemoer 2018-01-18 10:42:09 +00:00 committed by GitHub
commit 4f81d55ddf
32 changed files with 477 additions and 102 deletions

View File

@ -18,7 +18,7 @@
-- 32 bytes of random data, encoded in hexadecimal -- 32 bytes of random data, encoded in hexadecimal
-- Must be the same for all nodes in one mesh domain -- Must be the same for all nodes in one mesh domain
-- Can be generated using: echo $(hexdump -v -n 32 -e '1/1 "%02x"' </dev/urandom) -- Can be generated using: echo $(hexdump -v -n 32 -e '1/1 "%02x"' </dev/urandom)
site_seed = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', domain_seed = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
-- Prefixes used within the mesh. -- Prefixes used within the mesh.
-- prefix6 is required, prefix4 can be omitted if next_node.ip4 -- prefix6 is required, prefix4 can be omitted if next_node.ip4

View File

@ -21,7 +21,7 @@ site_code
The code of your community. It is good practice to use the TLD of The code of your community. It is good practice to use the TLD of
your community here. your community here.
site_seed domain_seed
32 bytes of random data, encoded in hexadecimal, used to seed other random 32 bytes of random data, encoded in hexadecimal, used to seed other random
values specific to the mesh domain. It must be the same for all nodes of one values specific to the mesh domain. It must be the same for all nodes of one
mesh, but should be different for firmwares that are not supposed to mesh with mesh, but should be different for firmwares that are not supposed to mesh with

View File

@ -1 +1 @@
need_string_array 'authorized_keys' need_string_array(in_site('authorized_keys'))

View File

@ -1,14 +1,14 @@
need_string 'autoupdater.branch' need_string(in_site('autoupdater.branch'))
local function check_branch(k, _) local function check_branch(k, conf_name)
assert_uci_name(k) assert_uci_name(k, conf_name)
local prefix = string.format('autoupdater.branches[%q].', k) local prefix = string.format('autoupdater.branches[%q].', k)
need_string(prefix .. 'name') need_string(in_site(prefix .. 'name'))
need_string_array_match(prefix .. 'mirrors', '^http://') need_string_array_match(prefix .. 'mirrors', '^http://')
need_number(prefix .. 'good_signatures') need_number(in_site(prefix .. 'good_signatures'))
need_string_array_match(prefix .. 'pubkeys', '^%x+$') need_string_array_match(in_site(prefix .. 'pubkeys'), '^%x+$')
end end
need_table('autoupdater.branches', check_branch) need_table('autoupdater.branches', check_branch)

View File

@ -1,10 +1,10 @@
need_string_match('next_node.mac', '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$', false) need_string_match(in_domain('next_node.mac'), '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$', false)
if need_string_match('next_node.ip4', '^%d+.%d+.%d+.%d+$', false) then if need_string_match(in_domain('next_node.ip4'), '^%d+.%d+.%d+.%d+$', false) then
need_string_match('prefix4', '^%d+.%d+.%d+.%d+/%d+$') need_string_match(in_domain('prefix4'), '^%d+.%d+.%d+.%d+/%d+$')
end end
need_string_match('next_node.ip6', '^[%x:]+$', false) need_string_match(in_domain('next_node.ip6'), '^[%x:]+$', false)
for _, config in ipairs({'wifi24', 'wifi5'}) do for _, config in ipairs({'wifi24', 'wifi5'}) do

View File

@ -1,3 +1,3 @@
if need_table('config_mode', nil, false) and need_table('config_mode.owner', nil, false) then if need_table(in_site('config_mode'), nil, false) and need_table(in_site('config_mode.owner'), nil, false) then
need_boolean('config_mode.owner.obligatory', false) need_boolean(in_site('config_mode.owner.obligatory'), false)
end end

View File

@ -1,3 +1,3 @@
if need_table('config_mode', nil, false) and need_table('config_mode.geo_location', nil, false) then if need_table(in_site('config_mode'), nil, false) and need_table(in_site('config_mode.geo_location'), nil, false) then
need_boolean('config_mode.geo_location.show_altitude', false) need_boolean(in_site('config_mode.geo_location.show_altitude'), false)
end end

View File

@ -1,36 +1,50 @@
need_string 'site_code' need_string(in_site('site_code'))
need_string 'site_name' need_string(in_site('site_name'))
need_string_match('site_seed', '^' .. ('%x'):rep(64) .. '$') need_string_match(in_domain('domain_seed'), '^' .. ('%x'):rep(64) .. '$')
need_string(in_site('default_domain_code'))
need_string(in_domain('domain_name'))
function check_alias(k, conf_name)
assert_uci_name(k, conf_name)
local path = string.format('domain_aliases[%q]', k)
need_string(in_domain(path))
end
need_table(in_domain('domain_aliases'), check_alias, false)
if need_table('opkg', nil, false) then if need_table('opkg', nil, false) then
need_string('opkg.lede', false) need_string('opkg.lede', false)
function check_repo(k, _) function check_repo(k, conf_name)
-- this is not actually a uci name, but using the same naming rules here is fine -- this is not actually a uci name, but using the same naming rules here is fine
assert_uci_name(k) assert_uci_name(k, conf_name)
need_string(string.format('opkg.extra[%q]', k)) local path = string.format('opkg.extra[%q]', k)
need_string(path)
end end
need_table('opkg.extra', check_repo, false) need_table('opkg.extra', check_repo, false)
end end
need_string('hostname_prefix', false) need_string(in_site('hostname_prefix'), false)
need_string 'timezone' need_string(in_site('timezone'))
need_string_array('ntp_servers', false) need_string_array('ntp_servers', false)
need_string_match('prefix6', '^[%x:]+/64$') need_string_match(in_domain('prefix6'), '^[%x:]+/64$')
for _, config in ipairs({'wifi24', 'wifi5'}) do for _, config in ipairs({'wifi24', 'wifi5'}) do
if need_table(config, nil, false) then if need_table(config, nil, false) then
need_string('regdom') -- regdom is only required when wifi24 or wifi5 is configured need_string(in_site('regdom')) -- regdom is only required when wifi24 or wifi5 is configured
need_number(config .. '.channel') need_number(config .. '.channel')
local rates = {1000, 2000, 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000} local rates = {1000, 2000, 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000}
local supported_rates = need_array_of(config .. '.supported_rates', rates, false) local supported_rates = need_array_of(in_site(config .. '.supported_rates'), rates, false)
if supported_rates then if supported_rates then
need_array_of(config .. '.basic_rate', supported_rates, true) need_array_of(config .. '.basic_rate', supported_rates, true)
else else
@ -39,36 +53,36 @@ for _, config in ipairs({'wifi24', 'wifi5'}) do
end end
end end
need_boolean('poe_passthrough', false) need_boolean(in_site('poe_passthrough'), false)
if need_table('dns', nil, false) then if need_table('dns', nil, false) then
need_number('dns.cacheentries', false) need_number('dns.cacheentries', false)
need_string_array_match('dns.servers', '^[%x:]+$', true) need_string_array_match('dns.servers', '^[%x:]+$', true)
end end
if need_table('next_node', nil, false) then if need_table('next_node', nil, false) then
need_string_match('next_node.ip6', '^[%x:]+$', false) need_string_match(in_domain('next_node.ip6'), '^[%x:]+$', false)
need_string_match('next_node.ip4', '^%d+.%d+.%d+.%d+$', false) need_string_match(in_domain('next_node.ip4'), '^%d+.%d+.%d+.%d+$', false)
end end
for _, config in ipairs({'wifi24', 'wifi5'}) do for _, config in ipairs({'wifi24', 'wifi5'}) do
local rates = {1000, 2000, 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000} local rates = {1000, 2000, 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000}
rates = need_array_of(config .. '.supported_rates', rates, false) or rates rates = need_array_of(in_site(config .. '.supported_rates'), rates, false) or rates
if need_table(config .. '.ibss', nil, false) then if need_table(config .. '.ibss', nil, false) then
need_string(config .. '.ibss.ssid') need_string(in_domain(config .. '.ibss.ssid'))
need_string_match(config .. '.ibss.bssid', '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$') need_string_match(in_domain(config .. '.ibss.bssid'), '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$')
need_one_of(config .. '.ibss.mcast_rate', rates, false) need_one_of(config .. '.ibss.mcast_rate', rates, false)
need_number(config .. '.ibss.vlan', false) need_number(config .. '.ibss.vlan', false)
need_boolean(config .. '.ibss.disabled', false) need_boolean(config .. '.ibss.disabled', false)
end end
if need_table(config .. '.mesh', nil, false) then if need_table(config .. '.mesh', nil, false) then
need_string(config .. '.mesh.id') need_string(in_domain(config .. '.mesh.id'))
need_one_of(config .. '.mesh.mcast_rate', rates, false) need_one_of(config .. '.mesh.mcast_rate', rates, false)
need_boolean(config .. '.mesh.disabled', false) need_boolean(config .. '.mesh.disabled', false)
end end
end end
need_boolean('mesh_on_wan', false) need_boolean(in_site('mesh_on_wan'), false)
need_boolean('mesh_on_lan', false) need_boolean(in_site('mesh_on_lan'), false)
need_boolean('single_as_lan', false) need_boolean(in_site('single_as_lan'), false)

View File

@ -0,0 +1 @@
config system system

View File

@ -0,0 +1,10 @@
#!/bin/sh
domain_code=$(uci get gluon.system.domain_code)
[ -f /lib/gluon/domains/${domain_code}.json ] || (echo "file not found: /lib/gluon/domains/${domain_code}.json" >&2; exit 1) || exit 1
for s in /lib/gluon/upgrade/*; do
echo -n ${s}:
(${s} && echo " ok") || echo " error"
done

View File

@ -44,7 +44,7 @@ proto_gluon_wired_setup() {
json_add_string tunlink "$config" json_add_string tunlink "$config"
json_add_string ip6addr "$(interface_linklocal "$ifname")" json_add_string ip6addr "$(interface_linklocal "$ifname")"
json_add_string peer6addr 'ff02::15c' json_add_string peer6addr 'ff02::15c'
json_add_int vid "$(lua -lgluon.util -e 'print(tonumber(gluon.util.site_seed_bytes("gluon-mesh-vxlan", 3), 16))')" json_add_int vid "$(lua -lgluon.util -e 'print(tonumber(gluon.util.domain_seed_bytes("gluon-mesh-vxlan", 3), 16))')"
json_close_object json_close_object
ubus call network add_dynamic "$(json_dump)" ubus call network add_dynamic "$(json_dump)"
fi fi

View File

@ -0,0 +1,5 @@
#!/usr/bin/lua
local jsonc = require 'luci.jsonc'
local site = require 'gluon.site'
print(jsonc.stringify(site(), true))

View File

@ -1,11 +1,10 @@
local site = (function() local function read_json(path)
local config = '/lib/gluon/site.json'
local json = require 'luci.jsonc' local json = require 'luci.jsonc'
local decoder = json.new() local decoder = json.new()
local sink = decoder:sink() local sink = decoder:sink()
local file = assert(io.open(config)) local file = assert(io.open(path))
while true do while true do
local chunk = file:read(2048) local chunk = file:read(2048)
@ -16,11 +15,40 @@ local site = (function()
file:close() file:close()
return assert(decoder:get()) return assert(decoder:get())
end)() end
local site = read_json('/lib/gluon/site.json')
local domain = (function(site)
local uci = require('simple-uci').cursor()
local fs = require "nixio.fs"
local sname = uci:get_first('gluon', 'system')
local dc = uci:get('gluon', sname, 'domain_code') or ''
if fs.stat('/lib/gluon/domains/'..dc..'.json', 'type')~='reg' then
dc = site['default_domain_code']
end
local domain = read_json('/lib/gluon/domains/'..dc..'.json')
if domain['domain_aliases'] and domain['domain_aliases'][dc] then
domain['domain_name'] = domain['domain_aliases'][dc]
end
return domain
end)(site)
local wrap local wrap
local function merge(t1, t2)
for k, v in pairs(t2) do
if (type(v) == "table") and (type(t1[k] or false) == "table") then
merge(t1[k], t2[k])
else
t1[k] = v
end
end
return t1
end
local function index(t, k) local function index(t, k)
local v = getmetatable(t).value local v = getmetatable(t).value
@ -58,4 +86,4 @@ end
module 'gluon.site' module 'gluon.site'
return wrap(site, _M) return wrap(merge(site, domain), _M)

View File

@ -122,7 +122,7 @@ function node_id()
return string.gsub(sysconfig.primary_mac, ':', '') return string.gsub(sysconfig.primary_mac, ':', '')
end end
function site_seed_bytes(key, length) function domain_seed_bytes(key, length)
local ret = '' local ret = ''
local v = '' local v = ''
local i = 0 local i = 0
@ -131,7 +131,7 @@ function site_seed_bytes(key, length)
-- cryptographic strength -- cryptographic strength
while ret:len() < 2*length do while ret:len() < 2*length do
i = i + 1 i = i + 1
v = hash.md5(v .. key .. site.site_seed():lower() .. i) v = hash.md5(v .. key .. site.domain_seed():lower() .. i)
ret = ret .. v ret = ret .. v
end end

View File

@ -1,2 +1,2 @@
need_string_match('prefix4', '^%d+.%d+.%d+.%d+/%d+$', false) need_string_match(in_domain('prefix4'), '^%d+.%d+.%d+.%d+/%d+$', false)
need_string_array_match('extra_prefixes6', '^[%x:]+/%d+$', false) need_string_array_match(in_domain('extra_prefixes6'), '^[%x:]+/%d+$', false)

View File

@ -1,23 +1,23 @@
local fastd_methods = {'salsa2012+gmac', 'salsa2012+umac', 'null+salsa2012+gmac', 'null+salsa2012+umac', 'null'} local fastd_methods = {'salsa2012+gmac', 'salsa2012+umac', 'null+salsa2012+gmac', 'null+salsa2012+umac', 'null'}
need_array_of('mesh_vpn.fastd.methods', fastd_methods) need_array_of('mesh_vpn.fastd.methods', fastd_methods)
need_boolean('mesh_vpn.fastd.configurable', false) need_boolean(in_site('mesh_vpn.fastd.configurable'), false)
need_one_of('mesh_vpn.fastd.syslog_level', {'error', 'warn', 'info', 'verbose', 'debug', 'debug2'}, false) need_one_of(in_site('mesh_vpn.fastd.syslog_level'), {'error', 'warn', 'info', 'verbose', 'debug', 'debug2'}, false)
local function check_peer(prefix) local function check_peer(prefix)
return function(k, _) return function(k, conf_name)
assert_uci_name(k) assert_uci_name(k, conf_name)
local table = string.format('%s[%q].', prefix, k) local table = string.format('%s[%q].', prefix, k)
need_string_match(table .. 'key', '^%x+$') need_string_match(table .. 'key', '^%x+$')
need_string_array(table .. 'remotes') need_string_array(in_domain(table .. 'remotes'))
end end
end end
local function check_group(prefix) local function check_group(prefix)
return function(k, _) return function(k, conf_name)
assert_uci_name(k) assert_uci_name(k, conf_name)
local table = string.format('%s[%q].', prefix, k) local table = string.format('%s[%q].', prefix, k)

View File

@ -1 +1 @@
need_string('roles.default', false) need_string(in_site('roles.default'), false)

View File

@ -67,6 +67,11 @@ static struct json_object * get_site_code(void) {
return ret; return ret;
} }
static struct json_object * get_domain_code(void) {
char * domain_code = gluonutil_get_selected_domain_code(NULL);
return gluonutil_wrap_and_free_string(domain_code);
}
static struct json_object * get_hostname(void) { static struct json_object * get_hostname(void) {
struct json_object *ret = NULL; struct json_object *ret = NULL;
@ -123,6 +128,7 @@ static struct json_object * respondd_provider_nodeinfo(void) {
struct json_object *system = json_object_new_object(); struct json_object *system = json_object_new_object();
json_object_object_add(system, "site_code", get_site_code()); json_object_object_add(system, "site_code", get_site_code());
json_object_object_add(system, "domain_code", get_domain_code());
json_object_object_add(ret, "system", system); json_object_object_add(ret, "system", system);
return ret; return ret;

View File

@ -1,2 +1 @@
need_boolean('setup_mode.skip', false) need_boolean(in_site('setup_mode.skip'), false)

View File

@ -8,7 +8,7 @@ PKG_VERSION:=$(if $(DUMP),x,$(GLUON_SITE_VERSION))
PKG_CONFIG_DEPENDS := CONFIG_GLUON_RELEASE CONFIG_GLUON_SITEDIR PKG_CONFIG_DEPENDS := CONFIG_GLUON_RELEASE CONFIG_GLUON_SITEDIR
PKG_FILE_DEPENDS := $(GLUON_SITEDIR)/site.conf $(GLUON_SITEDIR)/i18n/ PKG_FILE_DEPENDS := $(GLUON_SITEDIR)/site.conf $(GLUON_SITEDIR)/domains/ $(GLUON_SITEDIR)/i18n/
PKG_BUILD_DEPENDS := lua-cjson/host PKG_BUILD_DEPENDS := lua-cjson/host
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
@ -38,6 +38,7 @@ endef
define Build/Prepare define Build/Prepare
mkdir -p $(PKG_BUILD_DIR) mkdir -p $(PKG_BUILD_DIR)
mkdir -p $(PKG_BUILD_DIR)/domains
endef endef
define Build/Configure define Build/Configure
@ -45,12 +46,26 @@ endef
define Build/Compile define Build/Compile
GLUON_SITEDIR='$(call qstrip,$(CONFIG_GLUON_SITEDIR))' lua -e 'print(require("cjson").encode(assert(dofile("../../scripts/site_config.lua"))))' > $(PKG_BUILD_DIR)/site.json GLUON_SITEDIR='$(call qstrip,$(CONFIG_GLUON_SITEDIR))' lua -e 'print(require("cjson").encode(assert(dofile("../../scripts/site_config.lua"))))' > $(PKG_BUILD_DIR)/site.json
ls $(call qstrip,$(CONFIG_GLUON_SITEDIR))/domains/*.conf > /dev/null # at least one domain cfg has to exist
GLUON_SITEDIR='$(call qstrip,$(CONFIG_GLUON_SITEDIR))' lua -e 'print(assert(dofile("../../scripts/site_config.lua")).default_domain_code)' > $(PKG_BUILD_DIR)/default_domain_code
ls '$(call qstrip,$(CONFIG_GLUON_SITEDIR))'/domains/$$$$(cat $(PKG_BUILD_DIR)/default_domain_code).conf # ensure default_domain_code exists
rm -f $(PKG_BUILD_DIR)/domains/*.json
for domain_cfg in `find $(call qstrip,$(CONFIG_GLUON_SITEDIR))/domains/ -iname \*.conf -printf "%f\n"`; do \
dc=$$$${domain_cfg%.conf}; \
GLUON_SITEDIR='$(call qstrip,$(CONFIG_GLUON_SITEDIR))' lua -e 'print(require("cjson").encode(assert(dofile("../../scripts/domain_config.lua")("'$$$${dc}'"))))' > $(PKG_BUILD_DIR)/domains/$$$${dc}.json; \
aliases=$$$$(GLUON_SITEDIR='$(call qstrip,$(CONFIG_GLUON_SITEDIR))' lua -e 'for alias_name, _ in pairs(dofile("../../scripts/domain_config.lua")("'$$$${dc}'")["domain_aliases"] or {}) do print(alias_name.." ") end'); \
for alias in $$$${aliases}; do \
ln -s $$$${dc}.json $(PKG_BUILD_DIR)/domains/$$$${alias}.json; \
done; \
done
$(call GluonBuildI18N,gluon-site,$(GLUON_SITEDIR)/i18n) $(call GluonBuildI18N,gluon-site,$(GLUON_SITEDIR)/i18n)
endef endef
define Package/gluon-site/install define Package/gluon-site/install
$(INSTALL_DIR) $(1)/lib/gluon $(INSTALL_DIR) $(1)/lib/gluon
$(INSTALL_DATA) $(PKG_BUILD_DIR)/site.json $(1)/lib/gluon/ $(INSTALL_DATA) $(PKG_BUILD_DIR)/site.json $(1)/lib/gluon/
$(INSTALL_DIR) $(1)/lib/gluon/domains
$(CP) $(PKG_BUILD_DIR)/domains/*.json $(1)/lib/gluon/domains/
echo '$(GLUON_SITE_VERSION)' > $(1)/lib/gluon/site-version echo '$(GLUON_SITE_VERSION)' > $(1)/lib/gluon/site-version
echo '$(call qstrip,$(CONFIG_GLUON_RELEASE))' > $(1)/lib/gluon/release echo '$(call qstrip,$(CONFIG_GLUON_RELEASE))' > $(1)/lib/gluon/release

View File

@ -53,6 +53,7 @@ define(["lib/helper"], function (Helper) {
dlEntry(list, nodeInfo, "software.fastd.enabled", Helper._("Mesh VPN"), enabledDisabled) dlEntry(list, nodeInfo, "software.fastd.enabled", Helper._("Mesh VPN"), enabledDisabled)
dlEntry(list, nodeInfo, "software.autoupdater.enabled", Helper._("Automatic updates"), enabledDisabled) dlEntry(list, nodeInfo, "software.autoupdater.enabled", Helper._("Automatic updates"), enabledDisabled)
dlEntry(list, nodeInfo, "software.autoupdater.branch", Helper._("Branch")) dlEntry(list, nodeInfo, "software.autoupdater.branch", Helper._("Branch"))
dlEntry(list, nodeInfo, "system.domain_code", Helper._("Domain"))
el.appendChild(list) el.appendChild(list)
} }

View File

@ -1,4 +1,4 @@
if need_table('config_mode', nil, false) and need_table('config_mode.remote_login', nil, false) then if need_table(in_site('config_mode'), nil, false) and need_table(in_site('config_mode.remote_login'), nil, false) then
need_boolean('config_mode.remote_login.show_password_form', false) need_boolean(in_site('config_mode.remote_login.show_password_form'), false)
need_number('config_mode.remote_login.min_password_length', false) need_number(in_site('config_mode.remote_login.min_password_length'), false)
end end

View File

@ -1,2 +1,2 @@
assert(need_boolean('mesh_vpn.fastd.configurable') == true, assert(need_boolean(in_site('mesh_vpn.fastd.configurable')) == true,
"site.conf error: expected `mesh_vpn.fastd.configurable' to be true") "site.conf error: expected `mesh_vpn.fastd.configurable' to be true")

View File

@ -1,2 +1,2 @@
need_string 'roles.default' need_string(in_site('roles.default'))
need_string_array 'roles.list' need_string_array(in_site('roles.list'))

View File

@ -11,7 +11,24 @@ local site_json = f:read('*a')
f:close() f:close()
site = require('cjson').decode(site_json) site = require('cjson').decode(site_json)
$(shell cat '$(TOPDIR)/../scripts/check_site_lib.lua' '$(1)' | sed -ne '1h; 1!H; $$ {g; s/@/+@/g; s/\n/-@/g; p}')
function check_domain(domain_code, domain)
$(shell cat '$(TOPDIR)/../scripts/check_site_lib.lua' '$(1)' | sed -ne '1h; 1!H; $$ {g; s/@/+@/g; s/\n/-@/g; p}')
end
local dir = os.getenv('IPKG_INSTROOT') .. '/lib/gluon/domains/'
local pfile = io.popen('find '..dir..' -iname \\*.json')
for domain_path in pfile:lines() do
local domain_code = string.gmatch(domain_path, '([^/]+).json$$')()
local f = assert(io.open(domain_path))
local domain_json = f:read('*a')
f:close()
domain = require('cjson').decode(domain_json)
check_domain(domain_code, domain)
end
pfile:close()
END__GLUON__CHECK__SITE END__GLUON__CHECK__SITE
endef endef

View File

@ -16,7 +16,7 @@ define Package/libgluonutil
SECTION:=libs SECTION:=libs
CATEGORY:=Libraries CATEGORY:=Libraries
TITLE:=Gluon utility library TITLE:=Gluon utility library
DEPENDS:=+libjson-c DEPENDS:=+libjson-c +libuci
endef endef
CMAKE_OPTIONS += \ CMAKE_OPTIONS += \

View File

@ -7,14 +7,15 @@ project(libgluonutil C)
set(LIBDIR "lib${LIB_SUFFIX}") set(LIBDIR "lib${LIB_SUFFIX}")
find_package(JSON_C REQUIRED) find_package(JSON_C REQUIRED)
find_package(UCI REQUIRED)
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS _GNU_SOURCE) set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS _GNU_SOURCE)
add_library(gluonutil SHARED libgluonutil.c) add_library(gluonutil SHARED libgluonutil.c)
set_property(TARGET gluonutil PROPERTY COMPILE_FLAGS "-Wall -std=c99 ${JSON_C_CFLAGS_OTHER}") set_property(TARGET gluonutil PROPERTY COMPILE_FLAGS "-Wall -std=c99 ${JSON_C_CFLAGS_OTHER}")
set_property(TARGET gluonutil PROPERTY LINK_FLAGS "${JSON_C_LDFLAGS_OTHER}") set_property(TARGET gluonutil PROPERTY LINK_FLAGS "${JSON_C_LDFLAGS_OTHER}")
set_property(TARGET gluonutil APPEND PROPERTY INCLUDE_DIRECTORIES ${JSON_C_INCLUDE_DIR}) set_property(TARGET gluonutil APPEND PROPERTY INCLUDE_DIRECTORIES ${JSON_C_INCLUDE_DIR} ${UCI_INCLUDE_DIR})
target_link_libraries(gluonutil ${JSON_C_LIBRARIES}) target_link_libraries(gluonutil ${JSON_C_LIBRARIES} ${UCI_LIBRARIES})
install(TARGETS gluonutil install(TARGETS gluonutil
ARCHIVE DESTINATION ${LIBDIR} ARCHIVE DESTINATION ${LIBDIR}
LIBRARY DESTINATION ${LIBDIR} LIBRARY DESTINATION ${LIBDIR}

View File

@ -0,0 +1,21 @@
# UCI_FOUND - true if library and headers were found
# UCI_INCLUDE_DIRS - include directories
# UCI_LIBRARIES - library directories
find_package(PkgConfig)
pkg_check_modules(PC_UCI QUIET uci)
find_path(UCI_INCLUDE_DIR uci.h
HINTS ${PC_UCI_INCLUDEDIR} ${PC_UCI_INCLUDE_DIRS})
find_library(UCI_LIBRARY NAMES uci libuci
HINTS ${PC_UCI_LIBDIR} ${PC_UCI_LIBRARY_DIRS})
set(UCI_LIBRARIES ${UCI_LIBRARY})
set(UCI_INCLUDE_DIRS ${UCI_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(UCI DEFAULT_MSG UCI_LIBRARY UCI_INCLUDE_DIR)
mark_as_advanced(UCI_INCLUDE_DIR UCI_LIBRARY)

View File

@ -31,7 +31,41 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <uci.h>
/**
* Merges two JSON objects
*
* On conflicts, object a will be preferred.
*
* Internally, this functions merges all entries from object a into object b,
* so merging a small object a with a big object b is faster than vice-versa.
*/
static struct json_object * merge_json(struct json_object *a, struct json_object *b) {
if (!json_object_is_type(a, json_type_object) || !json_object_is_type(b, json_type_object)) {
json_object_put(b);
return a;
}
json_object_object_foreach(a, key, val_a) {
struct json_object *val_b;
json_object_get(val_a);
if (!json_object_object_get_ex(b, key, &val_b)) {
json_object_object_add(b, key, val_a);
continue;
}
json_object_get(val_b);
json_object_object_add(b, key, merge_json(val_a, val_b));
}
json_object_put(a);
return b;
}
char * gluonutil_read_line(const char *filename) { char * gluonutil_read_line(const char *filename) {
FILE *f = fopen(filename, "r"); FILE *f = fopen(filename, "r");
@ -140,7 +174,139 @@ bool gluonutil_get_node_prefix6(struct in6_addr *prefix) {
return true; return true;
} }
char * get_selected_domain_code(struct json_object * base) {
char * domain_path_fmt = "/lib/gluon/domains/%s.json";
char domain_path[strlen(domain_path_fmt) + 256];
const char * domain_code;
struct uci_context *ctx = uci_alloc_context();
if (!ctx)
goto uci_fail;
ctx->flags &= ~UCI_FLAG_STRICT;
struct uci_package *p;
if (uci_load(ctx, "gluon", &p))
goto uci_fail;
struct uci_section *s = uci_lookup_section(ctx, p, "system");
if (!s)
goto uci_fail;
domain_code = uci_lookup_option_string(ctx, s, "domain_code");
if (!domain_code)
goto uci_fail;
snprintf(domain_path, sizeof domain_path, domain_path_fmt, domain_code);
if (access(domain_path, R_OK) != -1) {
// ${domain_code}.conf exists and is accessible
char * domain_code_cpy = strndup(domain_code, 256); // copy before free
uci_free_context(ctx);
return domain_code_cpy;
}
uci_fail:
if (ctx)
uci_free_context(ctx);
json_object * default_domain_code;
// it's okay to pass base == NULL to json_object_object_get_ex()
if (!json_object_object_get_ex(base, "default_domain_code", &default_domain_code))
return NULL;
domain_code = json_object_get_string(default_domain_code);
if (!domain_code)
return NULL;
// the gluon build environment should ensure, that this filename exists,
// but to be sure, we check here again.
snprintf(domain_path, sizeof domain_path, domain_path_fmt, domain_code);
if (access(domain_path, R_OK) == -1)
return NULL;
// create a copy so site could be freed before domain_code
return strndup(domain_code, 256);
}
/**
* Get selected domain code
*
* - If NULL is passed to the site parameter, internally only the base part
* (without domain config) is loaded, which is more efficient than calling
* gluonutil_load_site_config() for this job only. Nevertheless if you already
* have an instance of a site object then you should pass it here.
* - Returned domain code string has to be freed after use
* - Returns NULL in case of error
* - If a domain code is returned, it's ensured that the corresponding config
* in /lib/gluon/domains/ exists.
*/
char * gluonutil_get_selected_domain_code(struct json_object * site) {
if (site)
// If the user already has allocated a whole site object, it makes no sense
// to load the base object. Taking the site object (merged from domain and
// base) should be fine here.
return get_selected_domain_code(site);
// load base
struct json_object * base = json_object_from_file("/lib/gluon/site.json");
if (!base)
return NULL;
return get_selected_domain_code(base);
}
struct json_object * gluonutil_load_site_config(void) { struct json_object * gluonutil_load_site_config(void) {
return json_object_from_file("/lib/gluon/site.json"); // load base
struct json_object * base = json_object_from_file("/lib/gluon/site.json");
if (!base)
return NULL;
// load domain
char * domain_path_fmt = "/lib/gluon/domains/%s.json";
char domain_path[strlen(domain_path_fmt) + 256];
char * domain_code = get_selected_domain_code(base);
if (!domain_code) {
// something went horribly wrong here
json_object_put(base); // free base
return NULL;
}
snprintf(domain_path, sizeof domain_path, domain_path_fmt, domain_code);
struct json_object * domain = json_object_from_file(domain_path);
if (!domain) {
json_object_put(base);
return NULL;
}
json_object * aliases;
// it's okay to pass base == NULL to json_object_object_get_ex()
if (!json_object_object_get_ex(domain, "domain_aliases", &aliases))
goto skip_name_replacement;
json_object * aliased_domain_name;
if (!json_object_object_get_ex(aliases, domain_code, &aliased_domain_name))
goto skip_name_replacement;
// freeing of old value is done inside json_object_object_add()
json_object_object_add(domain, "domain_name", json_object_get(aliased_domain_name));
skip_name_replacement:
free(domain_code);
// finally merge them
return merge_json(domain, base);
} }

View File

@ -42,4 +42,18 @@ struct json_object * gluonutil_wrap_and_free_string(char *str);
struct json_object * gluonutil_load_site_config(void); struct json_object * gluonutil_load_site_config(void);
/**
* Get selected domain code
*
* - If NULL is passed to the site parameter, internally only the base part
* (without domain config) is loaded, which is more efficient than calling
* gluonutil_load_site_config() for this job only. Nevertheless if you already
* have an instance of a site object then you should pass it here.
* - Returned domain code string has to be freed after use
* - Returns NULL in case of error
* - If a domain code is returned, it's ensured that the corresponding config
* in /lib/gluon/domains/ exists.
*/
char * gluonutil_get_selected_domain_code(struct json_object * site);
#endif /* _LIBGLUON_LIBGLUON_H_ */ #endif /* _LIBGLUON_LIBGLUON_H_ */

View File

@ -1,9 +1,32 @@
local function loadvar(varname) local function loadvar(varname)
local ok, val = pcall(assert(loadstring('return site.' .. varname))) local ok, val = pcall(assert(loadstring('return domain.' .. varname)))
if ok then if ok and val ~= nil then
return val return val, 'domains/'..domain_code..'.conf'
end
ok, val = pcall(assert(loadstring('return site.' .. varname)))
if ok and val ~= nil then
return val, 'site.conf'
else else
return nil return nil, 'site.conf or domains/'..domain_code..'.conf'
end
end
local function loadvar_domain(varname)
local ok, val = pcall(assert(loadstring('return domain.' .. varname)))
if ok then
return val, 'domains/'..domain_code..'.conf'
else
return nil, 'domains/'..domain_code..'.conf'
end
end
local function loadvar_site(varname)
ok, val = pcall(assert(loadstring('return site.' .. varname)))
if ok then
return val, 'site.conf'
else
return nil, 'site.conf'
end end
end end
@ -32,68 +55,87 @@ local function assert_type(var, t, msg)
assert(type(var) == t, msg) assert(type(var) == t, msg)
end end
-- returns an unique keys in keys of returned table
function keys_merged(a, b)
keys_table = {}
for k, _ in pairs(a or {}) do
keys_table[k] = 1
end
for k, _ in pairs(b or {}) do
keys_table[k] = 1
end
return keys_table
end
function assert_uci_name(var) function forbid_in_domain(varname)
local ok, val = pcall(assert(loadstring('return domain.' .. varname)))
assert(not ok or val == nil, "domains/"..domain_code..".conf error: `"..varname.."` is not allowed in domain specific config.")
end
function forbid_in_site(varname)
local ok, val = pcall(assert(loadstring('return site.' .. varname)))
assert(not ok or val == nil, "site.conf error: `"..varname.."` is not allowed in site config.")
end
function assert_uci_name(var, conf_name)
-- We don't use character classes like %w here to be independent of the locale -- We don't use character classes like %w here to be independent of the locale
assert(var:match('^[0-9a-zA-Z_]+$'), "site.conf error: `" .. var .. "' is not a valid config section name (only alphanumeric characters and the underscore are allowed)") assert(var:match('^[0-9a-zA-Z_]+$'), conf_name.." error: `" .. var .. "' is not a valid config section name (only alphanumeric characters and the underscore are allowed)")
end end
function need_string(varname, required) function need_string(varname, required)
local var = loadvar(varname) local var, conf_name = loadvar(varname)
if required == false and var == nil then if required == false and var == nil then
return nil return nil, conf_name
end end
assert_type(var, 'string', "site.conf error: expected `" .. varname .. "' to be a string") assert_type(var, 'string', conf_name .. " error: expected `" .. varname .. "' to be a string")
return var return var, conf_name
end end
function need_string_match(varname, pat, required) function need_string_match(varname, pat, required)
local var = need_string(varname, required) local var, conf_name = need_string(varname, required)
if not var then if not var then
return nil return nil
end end
assert(var:match(pat), "site.conf error: expected `" .. varname .. "' to match pattern `" .. pat .. "'") assert(var:match(pat), conf_name.." error: expected `" .. varname .. "' to match pattern `" .. pat .. "'")
return var return var
end end
function need_number(varname, required) function need_number(varname, required)
local var = loadvar(varname) local var, conf_name = loadvar(varname)
if required == false and var == nil then if required == false and var == nil then
return nil return nil
end end
assert_type(var, 'number', "site.conf error: expected `" .. varname .. "' to be a number") assert_type(var, 'number', conf_name.." error: expected `" .. varname .. "' to be a number")
return var return var
end end
function need_boolean(varname, required) function need_boolean(varname, required)
local var = loadvar(varname) local var, conf_name = loadvar(varname)
if required == false and var == nil then if required == false and var == nil then
return nil return nil
end end
assert_type(var, 'boolean', "site.conf error: expected `" .. varname .. "' to be a boolean") assert_type(var, 'boolean', conf_name.." error: expected `" .. varname .. "' to be a boolean")
return var return var
end end
function need_array(varname, subcheck, required) local function __need_array_from_var(var, varname, subcheck, required, conf_name)
local var = loadvar(varname)
if required == false and var == nil then if required == false and var == nil then
return nil return nil
end end
assert_type(var, 'table', "site.conf error: expected `" .. varname .. "' to be an array") assert_type(var, 'table', conf_name.." error: expected `" .. varname .. "' to be an array")
for _, e in ipairs(var) do for _, e in ipairs(var) do
subcheck(e) subcheck(e)
@ -102,18 +144,27 @@ function need_array(varname, subcheck, required)
return var return var
end end
function need_array(varname, subcheck, required)
local var, conf_name = loadvar(varname)
return __need_array_from_var(var, varname, subcheck, required, conf_name)
end
function need_table(varname, subcheck, required) function need_table(varname, subcheck, required)
local var = loadvar(varname) local var, conf_name = loadvar(varname)
if required == false and var == nil then if required == false and var == nil then
return nil return nil
end end
assert_type(var, 'table', "site.conf error: expected `" .. varname .. "' to be a table") assert_type(var, 'table', conf_name.." error: expected `" .. varname .. "' to be a table")
local dvar = loadvar_domain(varname)
local svar = loadvar_site(varname)
if subcheck then if subcheck then
for k, v in pairs(var) do for k, _ in pairs(keys_merged(dvar, svar)) do
subcheck(k, v) subcheck(k, conf_name)
end end
end end
@ -121,31 +172,44 @@ function need_table(varname, subcheck, required)
end end
function need_one_of(varname, array, required) function need_one_of(varname, array, required)
local var = loadvar(varname) local var, conf_name = loadvar(varname)
if required == false and var == nil then if required == false and var == nil then
return nil return nil
end end
assert_one_of(var, array, "site.conf error: expected `" .. varname .. "' to be one of given array: " .. array_to_string(array)) assert_one_of(var, array, conf_name.." error: expected `" .. varname .. "' to be one of given array: " .. array_to_string(array))
return var return var
end end
function need_string_array(varname, required) function need_string_array(varname, required)
local ok, var = pcall(need_array, varname, function(e) assert_type(e, 'string') end, required) local var, conf_name = loadvar(varname)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array") local ok, var = pcall(__need_array_from_var, var, varname, function(e) assert_type(e, 'string') end, required, conf_name)
assert(ok, conf_name.." error: expected `" .. varname .. "' to be a string array")
return var return var
end end
function need_string_array_match(varname, pat, required) function need_string_array_match(varname, pat, required)
local ok, var = pcall(need_array, varname, function(e) assert(e:match(pat)) end, required) local var, conf_name = loadvar(varname)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array matching pattern `" .. pat .. "'") local ok, var = pcall(__need_array_from_var, var, varname, function(e) assert(e:match(pat)) end, required, conf_name)
assert(ok, conf_name.." error: expected `" .. varname .. "' to be a string array matching pattern `" .. pat .. "'")
return var return var
end end
function need_array_of(varname, array, required) function need_array_of(varname, array, required)
local ok, var = pcall(need_array, varname, function(e) assert_one_of(e, array) end,required) local var, conf_name = loadvar(varname)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a subset of given array: " .. array_to_string(array)) local ok, var = pcall(__need_array_from_var, var, varname, function(e) assert_one_of(e, array) end, required, conf_name)
assert(ok, conf_name.." error: expected `" .. varname .. "' to be a subset of given array: " .. array_to_string(array))
return var
end
function in_domain(var)
forbid_in_site(var)
return var
end
function in_site(var)
forbid_in_domain(var)
return var return var
end end

13
scripts/domain_config.lua Normal file
View File

@ -0,0 +1,13 @@
local function load_domain(domain_code)
local config = os.getenv('GLUON_SITEDIR')
local function loader()
coroutine.yield('return ')
coroutine.yield(io.open(config .. '/domains/' .. domain_code .. '.conf'):read('*a'))
end
-- setfenv doesn't work with Lua 5.2 anymore, but we're using 5.1
return setfenv(assert(load(coroutine.wrap(loader), 'domains/' .. domain_code .. '.conf')), {})()
end
return load_domain