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
-- 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)
site_seed = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
domain_seed = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
-- Prefixes used within the mesh.
-- 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
your community here.
site_seed
domain_seed
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
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, _)
assert_uci_name(k)
local function check_branch(k, conf_name)
assert_uci_name(k, conf_name)
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_number(prefix .. 'good_signatures')
need_string_array_match(prefix .. 'pubkeys', '^%x+$')
need_number(in_site(prefix .. 'good_signatures'))
need_string_array_match(in_site(prefix .. 'pubkeys'), '^%x+$')
end
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
need_string_match('prefix4', '^%d+.%d+.%d+.%d+/%d+$')
if need_string_match(in_domain('next_node.ip4'), '^%d+.%d+.%d+.%d+$', false) then
need_string_match(in_domain('prefix4'), '^%d+.%d+.%d+.%d+/%d+$')
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

View File

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

View File

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

View File

@ -1,36 +1,50 @@
need_string 'site_code'
need_string 'site_name'
need_string_match('site_seed', '^' .. ('%x'):rep(64) .. '$')
need_string(in_site('site_code'))
need_string(in_site('site_name'))
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
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
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
need_table('opkg.extra', check_repo, false)
end
need_string('hostname_prefix', false)
need_string 'timezone'
need_string(in_site('hostname_prefix'), false)
need_string(in_site('timezone'))
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
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')
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
need_array_of(config .. '.basic_rate', supported_rates, true)
else
@ -39,36 +53,36 @@ for _, config in ipairs({'wifi24', 'wifi5'}) do
end
end
need_boolean('poe_passthrough', false)
need_boolean(in_site('poe_passthrough'), false)
if need_table('dns', nil, false) then
need_number('dns.cacheentries', false)
need_string_array_match('dns.servers', '^[%x:]+$', true)
end
if need_table('next_node', nil, false) then
need_string_match('next_node.ip6', '^[%x:]+$', false)
need_string_match('next_node.ip4', '^%d+.%d+.%d+.%d+$', false)
need_string_match(in_domain('next_node.ip6'), '^[%x:]+$', false)
need_string_match(in_domain('next_node.ip4'), '^%d+.%d+.%d+.%d+$', false)
end
for _, config in ipairs({'wifi24', 'wifi5'}) do
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
need_string(config .. '.ibss.ssid')
need_string_match(config .. '.ibss.bssid', '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$')
need_string(in_domain(config .. '.ibss.ssid'))
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_number(config .. '.ibss.vlan', false)
need_boolean(config .. '.ibss.disabled', false)
end
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_boolean(config .. '.mesh.disabled', false)
end
end
need_boolean('mesh_on_wan', false)
need_boolean('mesh_on_lan', false)
need_boolean('single_as_lan', false)
need_boolean(in_site('mesh_on_wan'), false)
need_boolean(in_site('mesh_on_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 ip6addr "$(interface_linklocal "$ifname")"
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
ubus call network add_dynamic "$(json_dump)"
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 config = '/lib/gluon/site.json'
local function read_json(path)
local json = require 'luci.jsonc'
local decoder = json.new()
local sink = decoder:sink()
local file = assert(io.open(config))
local file = assert(io.open(path))
while true do
local chunk = file:read(2048)
@ -16,11 +15,40 @@ local site = (function()
file:close()
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 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 v = getmetatable(t).value
@ -58,4 +86,4 @@ end
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, ':', '')
end
function site_seed_bytes(key, length)
function domain_seed_bytes(key, length)
local ret = ''
local v = ''
local i = 0
@ -131,7 +131,7 @@ function site_seed_bytes(key, length)
-- cryptographic strength
while ret:len() < 2*length do
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
end

View File

@ -1,2 +1,2 @@
need_string_match('prefix4', '^%d+.%d+.%d+.%d+/%d+$', false)
need_string_array_match('extra_prefixes6', '^[%x:]+/%d+$', false)
need_string_match(in_domain('prefix4'), '^%d+.%d+.%d+.%d+/%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'}
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)
return function(k, _)
assert_uci_name(k)
return function(k, conf_name)
assert_uci_name(k, conf_name)
local table = string.format('%s[%q].', prefix, k)
need_string_match(table .. 'key', '^%x+$')
need_string_array(table .. 'remotes')
need_string_array(in_domain(table .. 'remotes'))
end
end
local function check_group(prefix)
return function(k, _)
assert_uci_name(k)
return function(k, conf_name)
assert_uci_name(k, conf_name)
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;
}
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) {
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();
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);
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_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_DIR := $(BUILD_DIR)/$(PKG_NAME)
@ -38,6 +38,7 @@ endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
mkdir -p $(PKG_BUILD_DIR)/domains
endef
define Build/Configure
@ -45,12 +46,26 @@ endef
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
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)
endef
define Package/gluon-site/install
$(INSTALL_DIR) $(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 '$(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.autoupdater.enabled", Helper._("Automatic updates"), enabledDisabled)
dlEntry(list, nodeInfo, "software.autoupdater.branch", Helper._("Branch"))
dlEntry(list, nodeInfo, "system.domain_code", Helper._("Domain"))
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
need_boolean('config_mode.remote_login.show_password_form', false)
need_number('config_mode.remote_login.min_password_length', false)
if need_table(in_site('config_mode'), nil, false) and need_table(in_site('config_mode.remote_login'), nil, false) then
need_boolean(in_site('config_mode.remote_login.show_password_form'), false)
need_number(in_site('config_mode.remote_login.min_password_length'), false)
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")

View File

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

View File

@ -11,7 +11,24 @@ local site_json = f:read('*a')
f:close()
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
endef

View File

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

View File

@ -7,14 +7,15 @@ project(libgluonutil C)
set(LIBDIR "lib${LIB_SUFFIX}")
find_package(JSON_C REQUIRED)
find_package(UCI REQUIRED)
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS _GNU_SOURCE)
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 LINK_FLAGS "${JSON_C_LDFLAGS_OTHER}")
set_property(TARGET gluonutil APPEND PROPERTY INCLUDE_DIRECTORIES ${JSON_C_INCLUDE_DIR})
target_link_libraries(gluonutil ${JSON_C_LIBRARIES})
set_property(TARGET gluonutil APPEND PROPERTY INCLUDE_DIRECTORIES ${JSON_C_INCLUDE_DIR} ${UCI_INCLUDE_DIR})
target_link_libraries(gluonutil ${JSON_C_LIBRARIES} ${UCI_LIBRARIES})
install(TARGETS gluonutil
ARCHIVE 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 <string.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) {
FILE *f = fopen(filename, "r");
@ -140,7 +174,139 @@ bool gluonutil_get_node_prefix6(struct in6_addr *prefix) {
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) {
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);
/**
* 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_ */

View File

@ -1,9 +1,32 @@
local function loadvar(varname)
local ok, val = pcall(assert(loadstring('return site.' .. varname)))
if ok then
return val
local ok, val = pcall(assert(loadstring('return domain.' .. varname)))
if ok and val ~= nil then
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
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
@ -32,68 +55,87 @@ local function assert_type(var, t, msg)
assert(type(var) == t, msg)
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
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
function need_string(varname, required)
local var = loadvar(varname)
local var, conf_name = loadvar(varname)
if required == false and var == nil then
return nil
return nil, conf_name
end
assert_type(var, 'string', "site.conf error: expected `" .. varname .. "' to be a string")
return var
assert_type(var, 'string', conf_name .. " error: expected `" .. varname .. "' to be a string")
return var, conf_name
end
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
return nil
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
end
function need_number(varname, required)
local var = loadvar(varname)
local var, conf_name = loadvar(varname)
if required == false and var == nil then
return nil
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
end
function need_boolean(varname, required)
local var = loadvar(varname)
local var, conf_name = loadvar(varname)
if required == false and var == nil then
return nil
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
end
function need_array(varname, subcheck, required)
local var = loadvar(varname)
local function __need_array_from_var(var, varname, subcheck, required, conf_name)
if required == false and var == nil then
return nil
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
subcheck(e)
@ -102,18 +144,27 @@ function need_array(varname, subcheck, required)
return var
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)
local var = loadvar(varname)
local var, conf_name = loadvar(varname)
if required == false and var == nil then
return nil
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
for k, v in pairs(var) do
subcheck(k, v)
for k, _ in pairs(keys_merged(dvar, svar)) do
subcheck(k, conf_name)
end
end
@ -121,31 +172,44 @@ function need_table(varname, subcheck, required)
end
function need_one_of(varname, array, required)
local var = loadvar(varname)
local var, conf_name = loadvar(varname)
if required == false and var == nil then
return nil
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
end
function need_string_array(varname, required)
local ok, var = pcall(need_array, varname, function(e) assert_type(e, 'string') end, required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array")
local var, conf_name = loadvar(varname)
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
end
function need_string_array_match(varname, pat, required)
local ok, var = pcall(need_array, varname, function(e) assert(e:match(pat)) end, required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array matching pattern `" .. pat .. "'")
local var, conf_name = loadvar(varname)
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
end
function need_array_of(varname, array, required)
local ok, var = pcall(need_array, varname, function(e) assert_one_of(e, array) end,required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a subset of given array: " .. array_to_string(array))
local var, conf_name = loadvar(varname)
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
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