diff --git a/docs/site-example/site.conf b/docs/site-example/site.conf index 3d317cbf..a152eca6 100644 --- a/docs/site-example/site.conf +++ b/docs/site-example/site.conf @@ -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"' &2; exit 1) || exit 1 + +for s in /lib/gluon/upgrade/*; do + echo -n ${s}: + (${s} && echo " ok") || echo " error" +done diff --git a/package/gluon-core/files/lib/netifd/proto/gluon_wired.sh b/package/gluon-core/files/lib/netifd/proto/gluon_wired.sh index 9431bbce..46c4a124 100755 --- a/package/gluon-core/files/lib/netifd/proto/gluon_wired.sh +++ b/package/gluon-core/files/lib/netifd/proto/gluon_wired.sh @@ -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 diff --git a/package/gluon-core/luasrc/usr/bin/gluon-print-site b/package/gluon-core/luasrc/usr/bin/gluon-print-site new file mode 100755 index 00000000..3cec78de --- /dev/null +++ b/package/gluon-core/luasrc/usr/bin/gluon-print-site @@ -0,0 +1,5 @@ +#!/usr/bin/lua +local jsonc = require 'luci.jsonc' +local site = require 'gluon.site' + +print(jsonc.stringify(site(), true)) diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/site.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/site.lua index 56491763..3846b3ab 100644 --- a/package/gluon-core/luasrc/usr/lib/lua/gluon/site.lua +++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/site.lua @@ -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) diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua index 5ee6c376..a1da1768 100644 --- a/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua +++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua @@ -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 diff --git a/package/gluon-ebtables-source-filter/check_site.lua b/package/gluon-ebtables-source-filter/check_site.lua index 815c7296..d0e7ccfd 100644 --- a/package/gluon-ebtables-source-filter/check_site.lua +++ b/package/gluon-ebtables-source-filter/check_site.lua @@ -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) diff --git a/package/gluon-mesh-vpn-fastd/check_site.lua b/package/gluon-mesh-vpn-fastd/check_site.lua index 64da9c88..f2abf83c 100644 --- a/package/gluon-mesh-vpn-fastd/check_site.lua +++ b/package/gluon-mesh-vpn-fastd/check_site.lua @@ -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) diff --git a/package/gluon-node-info/check_site.lua b/package/gluon-node-info/check_site.lua index 7e50edfc..b7c4226d 100644 --- a/package/gluon-node-info/check_site.lua +++ b/package/gluon-node-info/check_site.lua @@ -1 +1 @@ -need_string('roles.default', false) +need_string(in_site('roles.default'), false) diff --git a/package/gluon-respondd/src/respondd.c b/package/gluon-respondd/src/respondd.c index 567d47c9..49db899d 100644 --- a/package/gluon-respondd/src/respondd.c +++ b/package/gluon-respondd/src/respondd.c @@ -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; diff --git a/package/gluon-setup-mode/check_site.lua b/package/gluon-setup-mode/check_site.lua index 07baaad3..b1df6a68 100644 --- a/package/gluon-setup-mode/check_site.lua +++ b/package/gluon-setup-mode/check_site.lua @@ -1,2 +1 @@ -need_boolean('setup_mode.skip', false) - +need_boolean(in_site('setup_mode.skip'), false) diff --git a/package/gluon-site/Makefile b/package/gluon-site/Makefile index 124dc49b..8b67b1d7 100644 --- a/package/gluon-site/Makefile +++ b/package/gluon-site/Makefile @@ -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 diff --git a/package/gluon-status-page/src/js/lib/gui/nodeinfo.js b/package/gluon-status-page/src/js/lib/gui/nodeinfo.js index 4fc08123..920b0f5a 100644 --- a/package/gluon-status-page/src/js/lib/gui/nodeinfo.js +++ b/package/gluon-status-page/src/js/lib/gui/nodeinfo.js @@ -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) } diff --git a/package/gluon-web-admin/check_site.lua b/package/gluon-web-admin/check_site.lua index 7fdce6fb..8c88d147 100644 --- a/package/gluon-web-admin/check_site.lua +++ b/package/gluon-web-admin/check_site.lua @@ -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 diff --git a/package/gluon-web-mesh-vpn-fastd/check_site.lua b/package/gluon-web-mesh-vpn-fastd/check_site.lua index b9e41e11..1704204a 100644 --- a/package/gluon-web-mesh-vpn-fastd/check_site.lua +++ b/package/gluon-web-mesh-vpn-fastd/check_site.lua @@ -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") diff --git a/package/gluon-web-node-role/check_site.lua b/package/gluon-web-node-role/check_site.lua index ab01eeb5..747915be 100644 --- a/package/gluon-web-node-role/check_site.lua +++ b/package/gluon-web-node-role/check_site.lua @@ -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')) diff --git a/package/gluon.mk b/package/gluon.mk index ab46dc22..e20d6bf9 100644 --- a/package/gluon.mk +++ b/package/gluon.mk @@ -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 diff --git a/package/libgluonutil/Makefile b/package/libgluonutil/Makefile index 489a9ba9..2536727c 100644 --- a/package/libgluonutil/Makefile +++ b/package/libgluonutil/Makefile @@ -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 += \ diff --git a/package/libgluonutil/src/CMakeLists.txt b/package/libgluonutil/src/CMakeLists.txt index ba92a3e1..ac8c0c49 100644 --- a/package/libgluonutil/src/CMakeLists.txt +++ b/package/libgluonutil/src/CMakeLists.txt @@ -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} diff --git a/package/libgluonutil/src/FindUCI.cmake b/package/libgluonutil/src/FindUCI.cmake new file mode 100644 index 00000000..820bc09b --- /dev/null +++ b/package/libgluonutil/src/FindUCI.cmake @@ -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) diff --git a/package/libgluonutil/src/libgluonutil.c b/package/libgluonutil/src/libgluonutil.c index db46b9c9..8510ef2a 100644 --- a/package/libgluonutil/src/libgluonutil.c +++ b/package/libgluonutil/src/libgluonutil.c @@ -31,7 +31,41 @@ #include #include #include +#include +#include +/** + * 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); } diff --git a/package/libgluonutil/src/libgluonutil.h b/package/libgluonutil/src/libgluonutil.h index b2f90d69..d464005c 100644 --- a/package/libgluonutil/src/libgluonutil.h +++ b/package/libgluonutil/src/libgluonutil.h @@ -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_ */ diff --git a/scripts/check_site_lib.lua b/scripts/check_site_lib.lua index 9e2bd135..a82e8352 100644 --- a/scripts/check_site_lib.lua +++ b/scripts/check_site_lib.lua @@ -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 diff --git a/scripts/domain_config.lua b/scripts/domain_config.lua new file mode 100644 index 00000000..1e166f66 --- /dev/null +++ b/scripts/domain_config.lua @@ -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