diff --git a/package/gluon-config-api/Makefile b/package/gluon-config-api/Makefile new file mode 100644 index 00000000..cc50a0db --- /dev/null +++ b/package/gluon-config-api/Makefile @@ -0,0 +1,16 @@ +# Copyright (C) 2021 Leonardo Moerlein +# This is free software, licensed under the Apache 2.0 license. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=gluon-config-api +PKG_VERSION:=1 + +include ../gluon.mk + +define Package/gluon-config-api + TITLE:=Provides a REST API to configure the gluon node + DEPENDS:=+gluon-web +uhttpd +libucl +endef + +$(eval $(call BuildPackageGluon,gluon-config-api)) diff --git a/package/gluon-config-api/luasrc/lib/gluon/config-api/controller/controller.lua b/package/gluon-config-api/luasrc/lib/gluon/config-api/controller/controller.lua new file mode 100644 index 00000000..28a3f80f --- /dev/null +++ b/package/gluon-config-api/luasrc/lib/gluon/config-api/controller/controller.lua @@ -0,0 +1,48 @@ +local json = require 'jsonc' +local site = require 'gluon.site' +local util = require 'gluon.util' +local ubus = require 'ubus' +local os = require 'os' +local glob = require 'posix.glob' +local libgen = require 'posix.libgen' +local simpleuci = require 'simple-uci' +local schema = require 'schema' + +package 'gluon-config-api' + +function load_parts() + local parts = {} + for _, f in pairs(glob.glob('/lib/gluon/config-api/parts/*.lua')) do + table.insert(parts, dofile(f)) + end + return parts +end + +function config_get(parts) + local config = {} + local uci = simpleuci.cursor() + + for _, part in pairs(parts) do + part.get(uci, config) + end + + return config +end + +local parts = load_parts() + + +entry({"config"}, call(function(http, renderer) + + http:write(json.stringify(config_get(parts), true)) + http:close() +end)) + +entry({"schema"}, call(function(http, renderer) + local total_schema = {} + for _, part in pairs(parts) do + total_schema = schema.merge_schemas(total_schema, part.schema()) + end + http:write(json.stringify(total_schema, true)) + http:close() +end)) diff --git a/package/gluon-config-api/luasrc/lib/gluon/config-api/controller/schema.lua b/package/gluon-config-api/luasrc/lib/gluon/config-api/controller/schema.lua new file mode 100644 index 00000000..1f2a5e93 --- /dev/null +++ b/package/gluon-config-api/luasrc/lib/gluon/config-api/controller/schema.lua @@ -0,0 +1,137 @@ + +local util = require 'gluon.util' + +local M = {} + +local function merge_types(Ta, Tb) + -- T == nil means "any" type is allowed + + if not Ta then return Tb end + if not Tb then return Ta end + + -- convert scalar types to arrays + if type(Ta) ~= 'table' then Ta = { Ta } end + if type(Tb) ~= 'table' then Tb = { Tb } end + + local Tnew = {} + + for _, t in pairs(Ta) do + if util.contains(Tb, t) then + table.insert(Tnew, t) + end + end + + assert(#Tnew > 0, 'ERROR: The schema does not match anything at all.') + + if #Tnew == 1 then + return Tnew[1] -- convert to scalar + else + return Tnew + end +end + +local function merged_keys(table1, table2) + local keys = {} + if table1 then + for k, _ in pairs(table1) do + table.insert(k) + end + end + if table2 then + for k, _ in pairs(table2) do + if not util.contains(k) then + table.insert(keys, k) + end + end + end + return keys +end + +local function merged_values(table1, table2) + local values = {} + if table1 then + for _, v in pairs(table1) do + table.insert(v) + end + end + if table2 then + for _, v in pairs(table2) do + if not util.contains(v) then + table.insert(values, v) + end + end + end + return values +end + +local function deepcopy(o, seen) + seen = seen or {} + if o == nil then return nil end + if seen[o] then return seen[o] end + + local no + if type(o) == 'table' then + no = {} + seen[o] = no + + for k, v in next, o, nil do + no[deepcopy(k, seen)] = deepcopy(v, seen) + end + setmetatable(no, deepcopy(getmetatable(o), seen)) + else -- number, string, boolean, etc + no = o + end + return no +end + + +function M.merge_schemas(schema1, schema2) + local merged = {} + + merged.type = merge_types(schema1.type, schema2.type) + + function add_property(pkey, pdef) + merged.properties = merged.properties or {} + merged.properties[pkey] = pdef + end + + if not merged.type or merged.type == 'object' then + -- generate merged.properties + local properties1 = schema1.properties or {} + local properties2 = schema2.properties or {} + + for _, pkey in pairs(merged_keys(properties1, properties2)) do + local pdef1 = properties1[pkey] + local pdef2 = properties2[pkey] + + if pdef1 and pdef2 then + add_property(pkey, merge_schemas(pdef1, pdef2)) + elseif pdef1 then + add_property(pkey, deepcopy(pdef1)) + elseif pdef2 then + add_property(pkey, deepcopy(pdef2)) + end + end + + -- generate merged.additionalProperties + if schema1.additionalProperties and schema2.additionalProperties then + merged.additionalProperties = merge_schemas( + schema1.additionalProperties, schema2.additionalProperties) + else + merged.additionalProperties = false + end + + -- generate merged.required + merged.required = merged_values(schema1, schema2) + if #merged.required == 0 then + merged.required = nil + end + end + + -- TODO: implement array + + -- generate merged.default + merged.default = schema2.default or schema1.default +end + +return M diff --git a/package/gluon-config-api/luasrc/lib/gluon/config-api/parts/contact-info.lua b/package/gluon-config-api/luasrc/lib/gluon/config-api/parts/contact-info.lua new file mode 100644 index 00000000..d0fb9973 --- /dev/null +++ b/package/gluon-config-api/luasrc/lib/gluon/config-api/parts/contact-info.lua @@ -0,0 +1,34 @@ + +local M = {} + +function M.schema(site, platform) + return { + type = 'object', + properties = { + wizard = { + type = 'object', + properties = { + contact = { + type = 'string' + } + } + } + } + } +end + +function M.save(config, uci) + local owner = uci:get_first("gluon-node-info", "owner") + + uci:set("gluon-node-info", owner, "contact", config.wizard.contact) + uci:save("gluon-node-info") +end + +function M.get(uci, config) + local owner = uci:get_first("gluon-node-info", "owner") + + config.wizard = config.wizard or {} + config.wizard.contact = uci:get("gluon-node-info", owner, "contact") +end + +return M diff --git a/package/gluon-config-api/luasrc/lib/gluon/config-api/www/cgi-bin/api b/package/gluon-config-api/luasrc/lib/gluon/config-api/www/cgi-bin/api new file mode 100755 index 00000000..a8881b28 --- /dev/null +++ b/package/gluon-config-api/luasrc/lib/gluon/config-api/www/cgi-bin/api @@ -0,0 +1,8 @@ +#!/usr/bin/lua + +require 'gluon.web.cgi' { + base_path = '/lib/gluon/config-api', + + layout_package = 'gluon-config-api', + layout_template = 'theme/layout', -- only used for error pages +} diff --git a/package/gluon-config-api/luasrc/lib/gluon/upgrade/500-config-api b/package/gluon-config-api/luasrc/lib/gluon/upgrade/500-config-api new file mode 100755 index 00000000..035de770 --- /dev/null +++ b/package/gluon-config-api/luasrc/lib/gluon/upgrade/500-config-api @@ -0,0 +1,36 @@ +#!/usr/bin/lua + +local uci = require('simple-uci').cursor() +local site = require 'gluon.site' + +local function get_mem_total() + for line in io.lines('/proc/meminfo') do + local match = line:match('^MemTotal:%s+(%d+)') + if match then + return tonumber(match) + end + end +end + +local max_requests = 32 +if get_mem_total() < 48*1024 then + max_requests = 16 +end + +uci:section('uhttpd', 'uhttpd', 'config_api', { + listen_http = { '0.0.0.0:83', '[::]:83' }, + listen_https = {}, + + home = '/lib/gluon/config-api/www', + max_requests = max_requests, + max_connections = 100, + redirect_https = true, + rfc1918_filter = true, + cgi_prefix = '/cgi-bin', + script_timeout = 60, + network_timeout = 30, + http_keepalive = 20, + tcp_keepalive = true, +}) +uci:save('uhttpd') +uci:save('firewall') diff --git a/package/libucl/Makefile b/package/libucl/Makefile new file mode 100644 index 00000000..db093034 --- /dev/null +++ b/package/libucl/Makefile @@ -0,0 +1,65 @@ +# +# Copyright (C) 2007-2014 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=libucl +PKG_VERSION:=0.8.1 +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/vstakhov/libucl +PKG_SOURCE_VERSION:=e6c5d8079b95796099693b0889f07a036f78ad77 +PKG_MAINTAINER:=Leonardo Mörlein + +PKG_LICENSE:=BSD-2-Clause + +PKG_FIXUP:=autoreconf +# PKG_FIXUP:=patch-libtool +# PKG_FIXUP:=gettext-version + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/autotools.mk + +CONFIGURE_ARGS += \ + --enable-lua + +define Package/libucl + SECTION:=libs + CATEGORY:=Libraries + TITLE:=Config Parsing + DEPENDS:=+liblua + URL:=https://github.com/vstakhov/libucl +endef + +define Package/libucl/description + Universal configuration library parser. +endef + +# MAKE_FLAGS += \ +# CFLAGS="$(TARGET_CFLAGS) $(FPIC)" + +define Package/libucl/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_BUILD_DIR)/src/.libs/libucl.so $(1)/usr/lib/ + $(CP) $(PKG_BUILD_DIR)/src/.libs/libucl.so.5 $(1)/usr/lib/ + $(CP) $(PKG_BUILD_DIR)/src/.libs/libucl.so.5.1.0 $(1)/usr/lib/ + $(INSTALL_DIR) $(1)/usr/lib/lua + $(CP) $(PKG_BUILD_DIR)/lua/.libs/ucl.so $(1)/usr/lib/lua/ucl.so +endef + + +# define Build/InstallDev +# $(INSTALL_DIR) $(1)/usr/include +# $(CP) $(PKG_BUILD_DIR)/argp.h \ +# $(1)/usr/include/ +# $(INSTALL_DIR) $(1)/usr/lib +# $(CP) $(PKG_BUILD_DIR)/libargp.a \ +# $(1)/usr/lib/ +# endef + +$(eval $(call BuildPackage,libucl))