New option install_contrib_modules

This option is read during ejabberd start or config reload.
It installs the listed modules which aren't yet installed,
as long as allow_contrib_modules is not disabled.
Edit ejabberd.yml and configure the desired ejabberd-contrib modules,
add them in the install_contrib_modules option,
finally start ejabberd (or reload config).
This commit is contained in:
Badlop 2023-05-17 17:09:09 +02:00
parent 3263e81972
commit c333cc0776
5 changed files with 84 additions and 13 deletions

View File

@ -502,7 +502,13 @@ read_file(File, Opts) ->
end,
case Ret of
{ok, Y} ->
validate(Y);
InstalledModules = maybe_install_contrib_modules(Y),
ValResult = validate(Y),
case InstalledModules of
[] -> ok;
_ -> spawn(fun() -> timer:sleep(5000), ?MODULE:reload() end)
end,
ValResult;
Err ->
Err
end.
@ -527,6 +533,18 @@ read_erlang_file(File, _) ->
Err
end.
-spec maybe_install_contrib_modules(term()) -> [atom()].
maybe_install_contrib_modules(Options) ->
case {lists:keysearch(allow_contrib_modules, 1, Options),
lists:keysearch(install_contrib_modules, 1, Options)} of
{Allow, {value, {_, InstallContribModules}}}
when (Allow == false) or
(Allow == {value, {allow_contrib_modules, true}}) ->
ext_mod:install_contrib_modules(InstallContribModules, Options);
_ ->
[]
end.
-spec validate(term()) -> {ok, [{atom(), term()}]} | error_return().
validate(Y1) ->
case pre_validate(Y1) of

View File

@ -52,6 +52,7 @@
-export([host_config/0]).
-export([hosts/0]).
-export([include_config_file/0, include_config_file/1]).
-export([install_contrib_modules/0]).
-export([jwt_auth_only_rule/0, jwt_auth_only_rule/1]).
-export([jwt_jid_field/0, jwt_jid_field/1]).
-export([jwt_key/0, jwt_key/1]).
@ -449,6 +450,10 @@ include_config_file() ->
include_config_file(Host) ->
ejabberd_config:get_option({include_config_file, Host}).
-spec install_contrib_modules() -> [atom()].
install_contrib_modules() ->
ejabberd_config:get_option({install_contrib_modules, global}).
-spec jwt_auth_only_rule() -> atom().
jwt_auth_only_rule() ->
jwt_auth_only_rule(global).

View File

@ -183,6 +183,8 @@ opt_type(hosts) ->
econf:non_empty(econf:list(econf:domain(), [unique]));
opt_type(include_config_file) ->
econf:any();
opt_type(install_contrib_modules) ->
econf:list(econf:atom());
opt_type(language) ->
econf:lang();
opt_type(ldap_backups) ->
@ -517,6 +519,7 @@ options() ->
{access_rules, []},
{acme, #{}},
{allow_contrib_modules, true},
{install_contrib_modules, []},
{allow_multiple_connections, false},
{anonymous_protocol, sasl_anon},
{api_permissions,
@ -736,6 +739,7 @@ globals() ->
fqdn,
hosts,
host_config,
install_contrib_modules,
listen,
loglevel,
log_rotate_count,

View File

@ -303,6 +303,8 @@ doc() ->
#{value => "true | false",
desc =>
?T("Whether to allow installation of third-party modules or not. "
"See https://docs.ejabberd.im/developer/extending-ejabberd/modules/#ejabberd-contrib"
"[ejabberd-contrib] documentation section. "
"The default value is 'true'.")}},
{allow_multiple_connections,
#{value => "true | false",
@ -670,6 +672,13 @@ doc() ->
"file 'Filename'. The options that do not match this "
"criteria are not accepted. The default value is to include "
"all options.")}}]},
{install_contrib_modules,
#{value => "[Module, ...]",
desc =>
?T("Modules to install from "
"https://docs.ejabberd.im/developer/extending-ejabberd/modules/#ejabberd-contrib"
"[ejabberd-contrib] at start time. "
"The default value is an empty list of modules: '[]'.")}},
{jwt_auth_only_rule,
#{value => ?T("AccessName"),
desc =>

View File

@ -33,6 +33,7 @@
installed_command/0, installed/0, installed/1,
install/1, uninstall/1, upgrade/0, upgrade/1, add_paths/0,
add_sources/1, add_sources/2, del_sources/1, modules_dir/0,
install_contrib_modules/2,
config_dir/0, get_commands_spec/0]).
-export([modules_configs/0, module_ebin_dir/1]).
-export([compile_erlang_file/2, compile_elixir_file/2]).
@ -215,10 +216,13 @@ installed_command() ->
[short_spec(Item) || Item <- installed()].
install(Module) when is_atom(Module) ->
install(misc:atom_to_binary(Module));
install(misc:atom_to_binary(Module), undefined);
install(Package) when is_binary(Package) ->
install(Package, undefined).
install(Package, Config) when is_binary(Package) ->
Spec = [S || {Mod, S} <- available(), misc:atom_to_binary(Mod)==Package],
case {Spec, installed(Package), is_contrib_allowed()} of
case {Spec, installed(Package), is_contrib_allowed(Config)} of
{_, _, false} ->
{error, not_allowed};
{[], _, _} ->
@ -227,10 +231,10 @@ install(Package) when is_binary(Package) ->
{error, conflict};
{[Attrs], _, _} ->
Module = misc:binary_to_atom(Package),
case compile_and_install(Module, Attrs) of
case compile_and_install(Module, Attrs, Config) of
ok ->
code:add_pathsz([module_ebin_dir(Module)|module_deps_dirs(Module)]),
ejabberd_config:reload(),
ejabberd_config_reload(Config),
copy_commit_json(Package, Attrs),
case erlang:function_exported(Module, post_install, 0) of
true -> Module:post_install();
@ -242,6 +246,14 @@ install(Package) when is_binary(Package) ->
end
end.
ejabberd_config_reload(Config) when is_list(Config) ->
%% Don't reload config when ejabberd is starting
%% because it will be reloaded after installing
%% all the external modules from install_contrib_modules
ok;
ejabberd_config_reload(undefined) ->
ejabberd_config:reload().
uninstall(Module) when is_atom(Module) ->
uninstall(misc:atom_to_binary(Module));
uninstall(Package) when is_binary(Package) ->
@ -483,7 +495,13 @@ modules_spec(Dir, Path) ->
short_spec({Module, Attrs}) when is_atom(Module), is_list(Attrs) ->
{Module, proplists:get_value(summary, Attrs, "")}.
is_contrib_allowed() ->
is_contrib_allowed(Config) when is_list(Config) ->
case lists:keyfind(allow_contrib_modules, 1, Config) of
false -> true;
{_, false} -> false;
{_, true} -> true
end;
is_contrib_allowed(undefined) ->
ejabberd_option:allow_contrib_modules().
%% -- build functions
@ -526,7 +544,7 @@ check_sources(Module) ->
_ -> {error, Result}
end.
compile_and_install(Module, Spec) ->
compile_and_install(Module, Spec, Config) ->
SrcDir = module_src_dir(Module),
LibDir = module_lib_dir(Module),
case filelib:is_dir(SrcDir) of
@ -534,7 +552,7 @@ compile_and_install(Module, Spec) ->
case compile_deps(SrcDir) of
ok ->
case compile(SrcDir) of
ok -> install(Module, Spec, SrcDir, LibDir);
ok -> install(Module, Spec, SrcDir, LibDir, Config);
Error -> Error
end;
Error ->
@ -543,7 +561,7 @@ compile_and_install(Module, Spec) ->
false ->
Path = proplists:get_value(url, Spec, ""),
case add_sources(Module, Path) of
ok -> compile_and_install(Module, Spec);
ok -> compile_and_install(Module, Spec, Config);
Error -> Error
end
end.
@ -648,7 +666,7 @@ compile_elixir_file(_, File) ->
{error, {compilation_failed, File}}.
-endif.
install(Module, Spec, SrcDir, LibDir) ->
install(Module, Spec, SrcDir, LibDir, Config) ->
{ok, CurDir} = file:get_cwd(),
file:set_cwd(SrcDir),
Files1 = [{File, copy(File, filename:join(LibDir, File))}
@ -660,7 +678,7 @@ install(Module, Spec, SrcDir, LibDir) ->
Errors = lists:dropwhile(fun({_, ok}) -> true;
(_) -> false
end, Files1++Files2++Files3),
inform_module_configuration(Module, LibDir, Files1),
inform_module_configuration(Module, LibDir, Files1, Config),
Result = case Errors of
[{F, {error, E}}|_] ->
{error, {F, E}};
@ -672,11 +690,11 @@ install(Module, Spec, SrcDir, LibDir) ->
file:set_cwd(CurDir),
Result.
inform_module_configuration(Module, LibDir, Files1) ->
inform_module_configuration(Module, LibDir, Files1, Config) ->
Res = lists:filter(fun({[$c, $o, $n, $f |_], ok}) -> true;
(_) -> false
end, Files1),
AlreadyConfigured = lists:keymember(Module, 1, ejabberd_config:get_option(modules)),
AlreadyConfigured = lists:keymember(Module, 1, get_modules(Config)),
case {Res, AlreadyConfigured} of
{[{ConfigPath, ok}], false} ->
FullConfigPath = filename:join(LibDir, ConfigPath),
@ -697,6 +715,12 @@ inform_module_configuration(Module, LibDir, Files1) ->
[Module])
end.
get_modules(Config) when is_list(Config) ->
{modules, Modules} = lists:keyfind(modules, 1, Config),
Modules;
get_modules(undefined) ->
ejabberd_config:get_option(modules).
%% -- minimalist rebar spec parser, only support git
fetch_rebar_deps(SrcDir) ->
@ -1220,3 +1244,14 @@ list_modules_parse_uninstall(Query) ->
end,
installed()),
ok.
install_contrib_modules(Modules, Config) ->
lists:filter(fun(Module) ->
case install(misc:atom_to_binary(Module), Config) of
{error, conflict} ->
false;
ok ->
true
end
end,
Modules).