mirror of
https://github.com/processone/ejabberd.git
synced 2024-06-02 21:17:12 +02:00
6f026ca26d
Support systemd's watchdog feature and enable it by default in the unit file, so that ejabberd is auto-restarted if the VM becomes unresponsive. Also, set the systemd startup type to 'notify', so that startup of followup units is delayed until ejabberd signals readiness. While at it, also notify systemd of configuration reload and shutdown states. Note: "NotifyAccess=all" is required as long as "ejabberdctl foreground" runs the VM as a new child process, rather than "exec"ing it. This way, systemd views the ejabberdctl process itself as the main service process, and would discard notifications from other processes by default.
772 lines
22 KiB
Erlang
772 lines
22 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% File : ejabberd_config.erl
|
|
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
|
%%% Purpose : Load config file
|
|
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
|
|
%%%
|
|
%%%
|
|
%%% ejabberd, Copyright (C) 2002-2020 ProcessOne
|
|
%%%
|
|
%%% This program is free software; you can redistribute it and/or
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
%%% License, or (at your option) any later version.
|
|
%%%
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
%%% General Public License for more details.
|
|
%%%
|
|
%%% You should have received a copy of the GNU General Public License along
|
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
%%%
|
|
%%%----------------------------------------------------------------------
|
|
-module(ejabberd_config).
|
|
|
|
%% API
|
|
-export([get_option/1]).
|
|
-export([load/0, reload/0, format_error/1, path/0]).
|
|
-export([env_binary_to_list/2]).
|
|
-export([get_myname/0, get_uri/0, get_copyright/0]).
|
|
-export([get_shared_key/0, get_node_start/0]).
|
|
-export([fsm_limit_opts/1]).
|
|
-export([codec_options/0]).
|
|
-export([version/0]).
|
|
-export([default_db/2, default_db/3, default_ram_db/2, default_ram_db/3]).
|
|
-export([beams/1, validators/1, globals/0, may_hide_data/1]).
|
|
-export([dump/0, dump/1, convert_to_yaml/1, convert_to_yaml/2]).
|
|
-export([callback_modules/1]).
|
|
|
|
%% Deprecated functions
|
|
-export([get_option/2, set_option/2]).
|
|
-export([get_version/0, get_myhosts/0]).
|
|
-export([get_mylang/0, get_lang/1]).
|
|
-deprecated([{get_option, 2},
|
|
{set_option, 2},
|
|
{get_version, 0},
|
|
{get_myhosts, 0},
|
|
{get_mylang, 0},
|
|
{get_lang, 1}]).
|
|
|
|
-include("logger.hrl").
|
|
-include("ejabberd_stacktrace.hrl").
|
|
|
|
-type option() :: atom() | {atom(), global | binary()}.
|
|
-type error_reason() :: {merge_conflict, atom(), binary()} |
|
|
{old_config, file:filename_all(), term()} |
|
|
{write_file, file:filename_all(), term()} |
|
|
{exception, term(), term(), term()}.
|
|
-type error_return() :: {error, econf:error_reason(), term()} |
|
|
{error, error_reason()}.
|
|
-type host_config() :: #{{atom(), binary() | global} => term()}.
|
|
|
|
-callback opt_type(atom()) -> econf:validator().
|
|
-callback options() -> [atom() | {atom(), term()}].
|
|
-callback globals() -> [atom()].
|
|
-callback doc() -> any().
|
|
|
|
-optional_callbacks([globals/0]).
|
|
|
|
%%%===================================================================
|
|
%%% API
|
|
%%%===================================================================
|
|
-spec load() -> ok | error_return().
|
|
load() ->
|
|
load(path()).
|
|
|
|
-spec load(file:filename_all()) -> ok | error_return().
|
|
load(Path) ->
|
|
ConfigFile = unicode:characters_to_binary(Path),
|
|
UnixTime = erlang:monotonic_time(second),
|
|
?INFO_MSG("Loading configuration from ~ts", [ConfigFile]),
|
|
_ = ets:new(ejabberd_options,
|
|
[named_table, public, {read_concurrency, true}]),
|
|
case load_file(ConfigFile) of
|
|
ok ->
|
|
set_shared_key(),
|
|
set_node_start(UnixTime),
|
|
?INFO_MSG("Configuration loaded successfully", []);
|
|
Err ->
|
|
Err
|
|
end.
|
|
|
|
-spec reload() -> ok | error_return().
|
|
reload() ->
|
|
ejabberd_systemd:reloading(),
|
|
ConfigFile = path(),
|
|
?INFO_MSG("Reloading configuration from ~ts", [ConfigFile]),
|
|
OldHosts = get_myhosts(),
|
|
Res = case load_file(ConfigFile) of
|
|
ok ->
|
|
NewHosts = get_myhosts(),
|
|
AddHosts = NewHosts -- OldHosts,
|
|
DelHosts = OldHosts -- NewHosts,
|
|
lists:foreach(
|
|
fun(Host) ->
|
|
ejabberd_hooks:run(host_up, [Host])
|
|
end, AddHosts),
|
|
lists:foreach(
|
|
fun(Host) ->
|
|
ejabberd_hooks:run(host_down, [Host])
|
|
end, DelHosts),
|
|
ejabberd_hooks:run(config_reloaded, []),
|
|
delete_host_options(DelHosts),
|
|
?INFO_MSG("Configuration reloaded successfully", []);
|
|
Err ->
|
|
?ERROR_MSG("Configuration reload aborted: ~ts",
|
|
[format_error(Err)]),
|
|
Err
|
|
end,
|
|
ejabberd_systemd:ready(),
|
|
Res.
|
|
|
|
-spec dump() -> ok | error_return().
|
|
dump() ->
|
|
dump(stdout).
|
|
|
|
-spec dump(stdout | file:filename_all()) -> ok | error_return().
|
|
dump(Output) ->
|
|
Y = get_option(yaml_config),
|
|
dump(Y, Output).
|
|
|
|
-spec dump(term(), stdout | file:filename_all()) -> ok | error_return().
|
|
dump(Y, Output) ->
|
|
Data = fast_yaml:encode(Y),
|
|
case Output of
|
|
stdout ->
|
|
io:format("~ts~n", [Data]);
|
|
FileName ->
|
|
try
|
|
ok = filelib:ensure_dir(FileName),
|
|
ok = file:write_file(FileName, Data)
|
|
catch _:{badmatch, {error, Reason}} ->
|
|
{error, {write_file, FileName, Reason}}
|
|
end
|
|
end.
|
|
|
|
-spec get_option(option(), term()) -> term().
|
|
get_option(Opt, Default) ->
|
|
try get_option(Opt)
|
|
catch _:badarg -> Default
|
|
end.
|
|
|
|
-spec get_option(option()) -> term().
|
|
get_option(Opt) when is_atom(Opt) ->
|
|
get_option({Opt, global});
|
|
get_option({O, Host} = Opt) ->
|
|
Tab = case get_tmp_config() of
|
|
undefined -> ejabberd_options;
|
|
T -> T
|
|
end,
|
|
try ets:lookup_element(Tab, Opt, 2)
|
|
catch ?EX_RULE(error, badarg, St) when Host /= global ->
|
|
StackTrace = ?EX_STACK(St),
|
|
Val = get_option({O, global}),
|
|
?DEBUG("Option '~ts' is not defined for virtual host '~ts'. "
|
|
"This is a bug, please report it with the following "
|
|
"stacktrace included:~n** ~ts",
|
|
[O, Host, misc:format_exception(2, error, badarg, StackTrace)]),
|
|
Val
|
|
end.
|
|
|
|
-spec set_option(option(), term()) -> ok.
|
|
set_option(Opt, Val) when is_atom(Opt) ->
|
|
set_option({Opt, global}, Val);
|
|
set_option(Opt, Val) ->
|
|
Tab = case get_tmp_config() of
|
|
undefined -> ejabberd_options;
|
|
T -> T
|
|
end,
|
|
ets:insert(Tab, {Opt, Val}),
|
|
ok.
|
|
|
|
-spec get_version() -> binary().
|
|
get_version() ->
|
|
get_option(version).
|
|
|
|
-spec get_myhosts() -> [binary(), ...].
|
|
get_myhosts() ->
|
|
get_option(hosts).
|
|
|
|
-spec get_myname() -> binary().
|
|
get_myname() ->
|
|
get_option(host).
|
|
|
|
-spec get_mylang() -> binary().
|
|
get_mylang() ->
|
|
get_lang(global).
|
|
|
|
-spec get_lang(global | binary()) -> binary().
|
|
get_lang(Host) ->
|
|
get_option({language, Host}).
|
|
|
|
-spec get_uri() -> binary().
|
|
get_uri() ->
|
|
<<"http://www.process-one.net/en/ejabberd/">>.
|
|
|
|
-spec get_copyright() -> binary().
|
|
get_copyright() ->
|
|
<<"Copyright (c) ProcessOne">>.
|
|
|
|
-spec get_shared_key() -> binary().
|
|
get_shared_key() ->
|
|
get_option(shared_key).
|
|
|
|
-spec get_node_start() -> integer().
|
|
get_node_start() ->
|
|
get_option(node_start).
|
|
|
|
-spec fsm_limit_opts([proplists:property()]) -> [{max_queue, pos_integer()}].
|
|
fsm_limit_opts(Opts) ->
|
|
case lists:keyfind(max_fsm_queue, 1, Opts) of
|
|
{_, I} when is_integer(I), I>0 ->
|
|
[{max_queue, I}];
|
|
false ->
|
|
case get_option(max_fsm_queue) of
|
|
undefined -> [];
|
|
N -> [{max_queue, N}]
|
|
end
|
|
end.
|
|
|
|
-spec codec_options() -> [xmpp:decode_option()].
|
|
codec_options() ->
|
|
case get_option(validate_stream) of
|
|
true -> [];
|
|
false -> [ignore_els]
|
|
end.
|
|
|
|
%% Do not use this function in runtime:
|
|
%% It's slow and doesn't read 'version' option from the config.
|
|
%% Use ejabberd_option:version() instead.
|
|
-spec version() -> binary().
|
|
version() ->
|
|
case application:get_env(ejabberd, custom_vsn) of
|
|
{ok, Vsn0} when is_list(Vsn0) ->
|
|
list_to_binary(Vsn0);
|
|
{ok, Vsn1} when is_binary(Vsn1) ->
|
|
Vsn1;
|
|
_ ->
|
|
case application:get_key(ejabberd, vsn) of
|
|
undefined -> <<"">>;
|
|
{ok, Vsn} -> list_to_binary(Vsn)
|
|
end
|
|
end.
|
|
|
|
-spec default_db(binary() | global, module()) -> atom().
|
|
default_db(Host, Module) ->
|
|
default_db(default_db, Host, Module, mnesia).
|
|
|
|
-spec default_db(binary() | global, module(), atom()) -> atom().
|
|
default_db(Host, Module, Default) ->
|
|
default_db(default_db, Host, Module, Default).
|
|
|
|
-spec default_ram_db(binary() | global, module()) -> atom().
|
|
default_ram_db(Host, Module) ->
|
|
default_db(default_ram_db, Host, Module, mnesia).
|
|
|
|
-spec default_ram_db(binary() | global, module(), atom()) -> atom().
|
|
default_ram_db(Host, Module, Default) ->
|
|
default_db(default_ram_db, Host, Module, Default).
|
|
|
|
-spec default_db(default_db | default_ram_db, binary() | global, module(), atom()) -> atom().
|
|
default_db(Opt, Host, Mod, Default) ->
|
|
Type = get_option({Opt, Host}),
|
|
DBMod = list_to_atom(atom_to_list(Mod) ++ "_" ++ atom_to_list(Type)),
|
|
case code:ensure_loaded(DBMod) of
|
|
{module, _} -> Type;
|
|
{error, _} ->
|
|
?WARNING_MSG("Module ~ts doesn't support database '~ts' "
|
|
"defined in option '~ts', using "
|
|
"'~ts' as fallback", [Mod, Type, Opt, Default]),
|
|
Default
|
|
end.
|
|
|
|
-spec beams(local | external | all) -> [module()].
|
|
beams(local) ->
|
|
{ok, Mods} = application:get_key(ejabberd, modules),
|
|
Mods;
|
|
beams(external) ->
|
|
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
|
|
lists:foreach(
|
|
fun(ExtMod) ->
|
|
ExtModPath = ext_mod:module_ebin_dir(ExtMod),
|
|
case lists:member(ExtModPath, code:get_path()) of
|
|
true -> ok;
|
|
false -> code:add_patha(ExtModPath)
|
|
end
|
|
end, ExtMods),
|
|
case application:get_env(ejabberd, external_beams) of
|
|
{ok, Path} ->
|
|
case lists:member(Path, code:get_path()) of
|
|
true -> ok;
|
|
false -> code:add_patha(Path)
|
|
end,
|
|
Beams = filelib:wildcard(filename:join(Path, "*\.beam")),
|
|
CustMods = [list_to_atom(filename:rootname(filename:basename(Beam)))
|
|
|| Beam <- Beams],
|
|
CustMods ++ ExtMods;
|
|
_ ->
|
|
ExtMods
|
|
end;
|
|
beams(all) ->
|
|
beams(local) ++ beams(external).
|
|
|
|
-spec may_hide_data(term()) -> term().
|
|
may_hide_data(Data) ->
|
|
case get_option(hide_sensitive_log_data) of
|
|
false -> Data;
|
|
true -> "hidden_by_ejabberd"
|
|
end.
|
|
|
|
%% Some Erlang apps expects env parameters to be list and not binary.
|
|
%% For example, Mnesia is not able to start if mnesia dir is passed as a binary.
|
|
%% However, binary is most common on Elixir, so it is easy to make a setup mistake.
|
|
-spec env_binary_to_list(atom(), atom()) -> {ok, any()} | undefined.
|
|
env_binary_to_list(Application, Parameter) ->
|
|
%% Application need to be loaded to allow setting parameters
|
|
application:load(Application),
|
|
case application:get_env(Application, Parameter) of
|
|
{ok, Val} when is_binary(Val) ->
|
|
BVal = binary_to_list(Val),
|
|
application:set_env(Application, Parameter, BVal),
|
|
{ok, BVal};
|
|
Other ->
|
|
Other
|
|
end.
|
|
|
|
-spec validators([atom()]) -> {econf:validators(), [atom()]}.
|
|
validators(Disallowed) ->
|
|
Modules = callback_modules(all),
|
|
Validators = lists:foldl(
|
|
fun(M, Vs) ->
|
|
maps:merge(Vs, validators(M, Disallowed))
|
|
end, #{}, Modules),
|
|
Required = lists:flatmap(
|
|
fun(M) ->
|
|
[O || O <- M:options(), is_atom(O)]
|
|
end, Modules),
|
|
{Validators, Required}.
|
|
|
|
-spec convert_to_yaml(file:filename()) -> ok | error_return().
|
|
convert_to_yaml(File) ->
|
|
convert_to_yaml(File, stdout).
|
|
|
|
-spec convert_to_yaml(file:filename(),
|
|
stdout | file:filename()) -> ok | error_return().
|
|
convert_to_yaml(File, Output) ->
|
|
case read_erlang_file(File, []) of
|
|
{ok, Y} ->
|
|
dump(Y, Output);
|
|
Err ->
|
|
Err
|
|
end.
|
|
|
|
-spec format_error(error_return()) -> string().
|
|
format_error({error, Reason, Ctx}) ->
|
|
econf:format_error(Reason, Ctx);
|
|
format_error({error, {merge_conflict, Opt, Host}}) ->
|
|
lists:flatten(
|
|
io_lib:format(
|
|
"Cannot merge value of option '~ts' defined in append_host_config "
|
|
"for virtual host ~ts: only options of type list or map are allowed "
|
|
"in append_host_config. Hint: specify the option in host_config",
|
|
[Opt, Host]));
|
|
format_error({error, {old_config, Path, Reason}}) ->
|
|
lists:flatten(
|
|
io_lib:format(
|
|
"Failed to read configuration from '~ts': ~ts~ts",
|
|
[Path,
|
|
case Reason of
|
|
{_, _, _} -> "at line ";
|
|
_ -> ""
|
|
end, file:format_error(Reason)]));
|
|
format_error({error, {write_file, Path, Reason}}) ->
|
|
lists:flatten(
|
|
io_lib:format(
|
|
"Failed to write to '~ts': ~ts",
|
|
[Path,
|
|
file:format_error(Reason)]));
|
|
format_error({error, {exception, Class, Reason, St}}) ->
|
|
lists:flatten(
|
|
io_lib:format(
|
|
"Exception occurred during configuration processing. "
|
|
"This is most likely due to faulty/incompatible validator in "
|
|
"third-party code. If you are not running any third-party "
|
|
"code, please report the bug with ejabberd configuration "
|
|
"file attached and the following stacktrace included:~n** ~ts",
|
|
[misc:format_exception(2, Class, Reason, St)])).
|
|
|
|
%%%===================================================================
|
|
%%% Internal functions
|
|
%%%===================================================================
|
|
-spec path() -> binary().
|
|
path() ->
|
|
unicode:characters_to_binary(
|
|
case get_env_config() of
|
|
{ok, Path} ->
|
|
Path;
|
|
undefined ->
|
|
case os:getenv("EJABBERD_CONFIG_PATH") of
|
|
false ->
|
|
"ejabberd.yml";
|
|
Path ->
|
|
Path
|
|
end
|
|
end).
|
|
|
|
-spec get_env_config() -> {ok, string()} | undefined.
|
|
get_env_config() ->
|
|
%% First case: the filename can be specified with: erl -config "/path/to/ejabberd.yml".
|
|
case application:get_env(ejabberd, config) of
|
|
R = {ok, _Path} -> R;
|
|
undefined ->
|
|
%% Second case for embbeding ejabberd in another app, for example for Elixir:
|
|
%% config :ejabberd,
|
|
%% file: "config/ejabberd.yml"
|
|
application:get_env(ejabberd, file)
|
|
end.
|
|
|
|
-spec create_tmp_config() -> ok.
|
|
create_tmp_config() ->
|
|
T = ets:new(options, [private]),
|
|
put(ejabberd_options, T),
|
|
ok.
|
|
|
|
-spec get_tmp_config() -> ets:tid() | undefined.
|
|
get_tmp_config() ->
|
|
get(ejabberd_options).
|
|
|
|
-spec delete_tmp_config() -> ok.
|
|
delete_tmp_config() ->
|
|
case get_tmp_config() of
|
|
undefined ->
|
|
ok;
|
|
T ->
|
|
erase(ejabberd_options),
|
|
ets:delete(T),
|
|
ok
|
|
end.
|
|
|
|
-spec callback_modules(local | external | all) -> [module()].
|
|
callback_modules(local) ->
|
|
[ejabberd_options];
|
|
callback_modules(external) ->
|
|
lists:filter(
|
|
fun(M) ->
|
|
case code:ensure_loaded(M) of
|
|
{module, _} ->
|
|
erlang:function_exported(M, options, 0)
|
|
andalso erlang:function_exported(M, opt_type, 1);
|
|
{error, _} ->
|
|
false
|
|
end
|
|
end, beams(external));
|
|
callback_modules(all) ->
|
|
callback_modules(local) ++ callback_modules(external).
|
|
|
|
-spec validators(module(), [atom()]) -> econf:validators().
|
|
validators(Mod, Disallowed) ->
|
|
maps:from_list(
|
|
lists:filtermap(
|
|
fun(O) ->
|
|
case lists:member(O, Disallowed) of
|
|
true -> false;
|
|
false ->
|
|
{true,
|
|
try {O, Mod:opt_type(O)}
|
|
catch _:_ ->
|
|
{O, ejabberd_options:opt_type(O)}
|
|
end}
|
|
end
|
|
end, proplists:get_keys(Mod:options()))).
|
|
|
|
read_file(File) ->
|
|
read_file(File, [replace_macros, include_files, include_modules_configs]).
|
|
|
|
read_file(File, Opts) ->
|
|
{Opts1, Opts2} = proplists:split(Opts, [replace_macros, include_files]),
|
|
Ret = case filename:extension(File) of
|
|
Ex when Ex == <<".yml">> orelse Ex == <<".yaml">> ->
|
|
Files = case proplists:get_bool(include_modules_configs, Opts2) of
|
|
true -> ext_mod:modules_configs();
|
|
false -> []
|
|
end,
|
|
lists:foreach(
|
|
fun(F) ->
|
|
?INFO_MSG("Loading third-party configuration from ~ts", [F])
|
|
end, Files),
|
|
read_yaml_files([File|Files], lists:flatten(Opts1));
|
|
_ ->
|
|
read_erlang_file(File, lists:flatten(Opts1))
|
|
end,
|
|
case Ret of
|
|
{ok, Y} ->
|
|
validate(Y);
|
|
Err ->
|
|
Err
|
|
end.
|
|
|
|
read_yaml_files(Files, Opts) ->
|
|
ParseOpts = [plain_as_atom | lists:flatten(Opts)],
|
|
lists:foldl(
|
|
fun(File, {ok, Y1}) ->
|
|
case econf:parse(File, #{'_' => econf:any()}, ParseOpts) of
|
|
{ok, Y2} -> {ok, Y1 ++ Y2};
|
|
Err -> Err
|
|
end;
|
|
(_, Err) ->
|
|
Err
|
|
end, {ok, []}, Files).
|
|
|
|
read_erlang_file(File, _) ->
|
|
case ejabberd_old_config:read_file(File) of
|
|
{ok, Y} ->
|
|
econf:replace_macros(Y);
|
|
Err ->
|
|
Err
|
|
end.
|
|
|
|
-spec validate(term()) -> {ok, [{atom(), term()}]} | error_return().
|
|
validate(Y1) ->
|
|
case pre_validate(Y1) of
|
|
{ok, Y2} ->
|
|
set_loglevel(proplists:get_value(loglevel, Y2, info)),
|
|
case ejabberd_config_transformer:map_reduce(Y2) of
|
|
{ok, Y3} ->
|
|
Hosts = proplists:get_value(hosts, Y3),
|
|
Version = proplists:get_value(version, Y3, version()),
|
|
create_tmp_config(),
|
|
set_option(hosts, Hosts),
|
|
set_option(host, hd(Hosts)),
|
|
set_option(version, Version),
|
|
set_option(yaml_config, Y3),
|
|
{Validators, Required} = validators([]),
|
|
Validator = econf:options(Validators,
|
|
[{required, Required},
|
|
unique]),
|
|
econf:validate(Validator, Y3);
|
|
Err ->
|
|
Err
|
|
end;
|
|
Err ->
|
|
Err
|
|
end.
|
|
|
|
-spec pre_validate(term()) -> {ok, [{atom(), term()}]} | error_return().
|
|
pre_validate(Y1) ->
|
|
econf:validate(
|
|
econf:and_then(
|
|
econf:options(
|
|
#{hosts => ejabberd_options:opt_type(hosts),
|
|
loglevel => ejabberd_options:opt_type(loglevel),
|
|
version => ejabberd_options:opt_type(version),
|
|
'_' => econf:any()},
|
|
[{required, [hosts]}]),
|
|
fun econf:group_dups/1), Y1).
|
|
|
|
-spec load_file(binary()) -> ok | error_return().
|
|
load_file(File) ->
|
|
try
|
|
case read_file(File) of
|
|
{ok, Terms} ->
|
|
case set_host_config(Terms) of
|
|
{ok, Map} ->
|
|
T = get_tmp_config(),
|
|
Hosts = get_myhosts(),
|
|
apply_defaults(T, Hosts, Map),
|
|
case validate_modules(Hosts) of
|
|
{ok, ModOpts} ->
|
|
ets:insert(T, ModOpts),
|
|
set_option(host, hd(Hosts)),
|
|
commit(),
|
|
set_fqdn();
|
|
Err ->
|
|
abort(Err)
|
|
end;
|
|
Err ->
|
|
abort(Err)
|
|
end;
|
|
Err ->
|
|
abort(Err)
|
|
end
|
|
catch ?EX_RULE(Class, Reason, St) ->
|
|
{error, {exception, Class, Reason, ?EX_STACK(St)}}
|
|
end.
|
|
|
|
-spec commit() -> ok.
|
|
commit() ->
|
|
T = get_tmp_config(),
|
|
NewOpts = ets:tab2list(T),
|
|
ets:insert(ejabberd_options, NewOpts),
|
|
delete_tmp_config().
|
|
|
|
-spec abort(error_return()) -> error_return().
|
|
abort(Err) ->
|
|
delete_tmp_config(),
|
|
try ets:lookup_element(ejabberd_options, {loglevel, global}, 2) of
|
|
Level -> set_loglevel(Level)
|
|
catch _:badarg ->
|
|
ok
|
|
end,
|
|
Err.
|
|
|
|
-spec set_host_config([{atom(), term()}]) -> {ok, host_config()} | error_return().
|
|
set_host_config(Opts) ->
|
|
Map1 = lists:foldl(
|
|
fun({Opt, Val}, M) when Opt /= host_config,
|
|
Opt /= append_host_config ->
|
|
maps:put({Opt, global}, Val, M);
|
|
(_, M) ->
|
|
M
|
|
end, #{}, Opts),
|
|
HostOpts = proplists:get_value(host_config, Opts, []),
|
|
AppendHostOpts = proplists:get_value(append_host_config, Opts, []),
|
|
Map2 = lists:foldl(
|
|
fun({Host, Opts1}, M1) ->
|
|
lists:foldl(
|
|
fun({Opt, Val}, M2) ->
|
|
maps:put({Opt, Host}, Val, M2)
|
|
end, M1, Opts1)
|
|
end, Map1, HostOpts),
|
|
Map3 = lists:foldl(
|
|
fun(_, {error, _} = Err) ->
|
|
Err;
|
|
({Host, Opts1}, M1) ->
|
|
lists:foldl(
|
|
fun(_, {error, _} = Err) ->
|
|
Err;
|
|
({Opt, L1}, M2) when is_list(L1) ->
|
|
L2 = try maps:get({Opt, Host}, M2)
|
|
catch _:{badkey, _} ->
|
|
maps:get({Opt, global}, M2, [])
|
|
end,
|
|
L3 = L2 ++ L1,
|
|
maps:put({Opt, Host}, L3, M2);
|
|
({Opt, _}, _) ->
|
|
{error, {merge_conflict, Opt, Host}}
|
|
end, M1, Opts1)
|
|
end, Map2, AppendHostOpts),
|
|
case Map3 of
|
|
{error, _} -> Map3;
|
|
_ -> {ok, Map3}
|
|
end.
|
|
|
|
-spec apply_defaults(ets:tid(), [binary()], host_config()) -> ok.
|
|
apply_defaults(Tab, Hosts, Map) ->
|
|
Defaults1 = defaults(),
|
|
apply_defaults(Tab, global, Map, Defaults1),
|
|
{_, Defaults2} = proplists:split(Defaults1, globals()),
|
|
lists:foreach(
|
|
fun(Host) ->
|
|
set_option(host, Host),
|
|
apply_defaults(Tab, Host, Map, Defaults2)
|
|
end, Hosts).
|
|
|
|
-spec apply_defaults(ets:tid(), global | binary(),
|
|
host_config(),
|
|
[atom() | {atom(), term()}]) -> ok.
|
|
apply_defaults(Tab, Host, Map, Defaults) ->
|
|
lists:foreach(
|
|
fun({Opt, Default}) ->
|
|
try maps:get({Opt, Host}, Map) of
|
|
Val ->
|
|
ets:insert(Tab, {{Opt, Host}, Val})
|
|
catch _:{badkey, _} when Host == global ->
|
|
Default1 = compute_default(Default, Host),
|
|
ets:insert(Tab, {{Opt, Host}, Default1});
|
|
_:{badkey, _} ->
|
|
try maps:get({Opt, global}, Map) of
|
|
V -> ets:insert(Tab, {{Opt, Host}, V})
|
|
catch _:{badkey, _} ->
|
|
Default1 = compute_default(Default, Host),
|
|
ets:insert(Tab, {{Opt, Host}, Default1})
|
|
end
|
|
end;
|
|
(Opt) when Host == global ->
|
|
Val = maps:get({Opt, Host}, Map),
|
|
ets:insert(Tab, {{Opt, Host}, Val});
|
|
(_) ->
|
|
ok
|
|
end, Defaults).
|
|
|
|
-spec defaults() -> [atom() | {atom(), term()}].
|
|
defaults() ->
|
|
lists:foldl(
|
|
fun(Mod, Acc) ->
|
|
lists:foldl(
|
|
fun({Opt, Val}, Acc1) ->
|
|
lists:keystore(Opt, 1, Acc1, {Opt, Val});
|
|
(Opt, Acc1) ->
|
|
case lists:member(Opt, Acc1) of
|
|
true -> Acc1;
|
|
false -> [Opt|Acc1]
|
|
end
|
|
end, Acc, Mod:options())
|
|
end, ejabberd_options:options(), callback_modules(external)).
|
|
|
|
-spec globals() -> [atom()].
|
|
globals() ->
|
|
lists:usort(
|
|
lists:flatmap(
|
|
fun(Mod) ->
|
|
case erlang:function_exported(Mod, globals, 0) of
|
|
true -> Mod:globals();
|
|
false -> []
|
|
end
|
|
end, callback_modules(all))).
|
|
|
|
%% The module validator depends on virtual host, so we have to
|
|
%% validate modules in this separate function.
|
|
-spec validate_modules([binary()]) -> {ok, list()} | error_return().
|
|
validate_modules(Hosts) ->
|
|
lists:foldl(
|
|
fun(Host, {ok, Acc}) ->
|
|
set_option(host, Host),
|
|
ModOpts = get_option({modules, Host}),
|
|
case gen_mod:validate(Host, ModOpts) of
|
|
{ok, ModOpts1} ->
|
|
{ok, [{{modules, Host}, ModOpts1}|Acc]};
|
|
Err ->
|
|
Err
|
|
end;
|
|
(_, Err) ->
|
|
Err
|
|
end, {ok, []}, Hosts).
|
|
|
|
-spec delete_host_options([binary()]) -> ok.
|
|
delete_host_options(Hosts) ->
|
|
lists:foreach(
|
|
fun(Host) ->
|
|
ets:match_delete(ejabberd_options, {{'_', Host}, '_'})
|
|
end, Hosts).
|
|
|
|
-spec compute_default(fun((global | binary()) -> T) | T, global | binary()) -> T.
|
|
compute_default(F, Host) when is_function(F, 1) ->
|
|
F(Host);
|
|
compute_default(Val, _) ->
|
|
Val.
|
|
|
|
-spec set_fqdn() -> ok.
|
|
set_fqdn() ->
|
|
FQDNs = get_option(fqdn),
|
|
xmpp:set_config([{fqdn, FQDNs}]).
|
|
|
|
-spec set_shared_key() -> ok.
|
|
set_shared_key() ->
|
|
Key = case erlang:get_cookie() of
|
|
nocookie ->
|
|
str:sha(p1_rand:get_string());
|
|
Cookie ->
|
|
str:sha(erlang:atom_to_binary(Cookie, latin1))
|
|
end,
|
|
set_option(shared_key, Key).
|
|
|
|
-spec set_node_start(integer()) -> ok.
|
|
set_node_start(UnixTime) ->
|
|
set_option(node_start, UnixTime).
|
|
|
|
-spec set_loglevel(logger:level()) -> ok.
|
|
set_loglevel(Level) ->
|
|
ejabberd_logger:set(Level).
|