%% @author Marc Worrell <marc@worrell.nl>
%% @copyright 2017-2021 Marc Worrell
%% @doc Zotonic core - main routines to 'reason' about the current Zotonic
%% installation.
%% @end
%% Copyright 2017-2021 Marc Worrell
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(zotonic_core).
-author('Marc Worrell <marc@worrell.nl>').
-include_lib("kernel/include/logger.hrl").
-export([
is_zotonic_project/0,
is_testsandbox/0,
is_app_available/1,
setup/1
]).
%% @doc Check if the current site is running the testsandbox
-spec is_testsandbox() -> boolean().
is_testsandbox() ->
case atom_to_list(node()) of
"zotonic001_testsandbox@" ++ _ -> true;
_ -> false
end.
%% @doc Check if this running Zotonic is the main Git project.
%% This is used for e.g. the .pot generation.
is_zotonic_project() ->
is_app_available(zotonic)
andalso is_app_available(zotonic_core).
is_app_available(App) ->
case code:which(App) of
non_existing -> false;
Path when is_list(Path) -> true
end.
%% @doc Initial setup before starting Zotonic, after config files are loaded. This
%% is normally called by the zotonic_launcher_app, which also reads all config files.
-spec setup( node() ) -> ok.
setup(Node) ->
io:setopts([{encoding, unicode}]),
maybe_start_logstasher(),
assert_schedulers( erlang:system_info(schedulers) ),
load_applications(),
set_configs(),
case node() of
Node -> ensure_mnesia_schema();
_ -> ok
end.
%% @doc Load the applications so that their settings are also loaded.
load_applications() ->
application:load(setup),
application:load(mnesia),
application:load(ranch),
application:load(filezcache),
application:load(zotonic_core).
set_configs() ->
application:set_env(setup, log_dir, z_config:get(log_dir)),
application:set_env(setup, data_dir, z_config:get(data_dir)),
% Ensure ranch is not stopping on client (TLS handshake) errors
case application:get_env(ranch, ranch_sup_intensity) of
{ok, _} -> ok;
undefined -> application:set_env(ranch, ranch_sup_intensity, 10_000_000)
end,
case application:get_env(ranch, ranch_sup_period) of
{ok, _} -> ok;
undefined -> application:set_env(ranch, ranch_sup_period, 1)
end,
% Store filezcache data in the cache_dir.
FileZCache = filename:join([ z_config:get(cache_dir), "filezcache", atom_to_list(node()) ]),
application:set_env(filezcache, data_dir, filename:join([ FileZCache, "data" ])),
application:set_env(filezcache, journal_dir, filename:join([ FileZCache, "journal" ])).
maybe_start_logstasher() ->
IsLogstasherNeeded = lists:any(
fun(#{ module := Module }) ->
Module =:= logstasher_h
end,
logger:get_handler_config()),
case IsLogstasherNeeded of
true ->
application:ensure_all_started(logstasher, permanent);
false ->
ok
end.
%% @doc Refuse to start if only 1 scheduler active. Bcrypt needs separate schedulers.
assert_schedulers(1) ->
io:format("FATAL: Not enough schedulers, please start with 2 or more schedulers.~nUse: ERLOPTS=\"+S 4:4\" ./bin/zotonic debug~n~n"),
erlang:halt();
assert_schedulers(_N) ->
ok.
%% @doc Ensure that mnesia has created its schema in the configured data/mnesia directory.
-spec ensure_mnesia_schema() -> ok.
ensure_mnesia_schema() ->
case mnesia_dir() of
{ok, Dir} ->
case filelib:is_dir(Dir) andalso filelib:is_regular(filename:join(Dir, "schema.DAT")) of
true -> ok;
false -> ok = mnesia:create_schema([node()])
end;
undefined ->
?LOG_NOTICE("No mnesia directory defined, running without persistent email queue and filezcache. "
"To enable persistency, add to erlang.config: {mnesia,[{dir,\"data/mnesia\"}]}"),
ok
end.
mnesia_dir() ->
case is_testsandbox() of
true ->
application:unset_env(mnesia, dir),
undefined;
false ->
mnesia_dir_config()
end.
mnesia_dir_config() ->
case application:get_env(mnesia, dir) of
{ok, none} -> undefined;
{ok, ""} -> undefined;
{ok, setup} -> mnesia_data_dir();
{ok, "data/mnesia"} -> mnesia_data_dir();
{ok, "priv/mnesia"} -> mnesia_data_dir();
{ok, Dir} -> {ok, Dir};
undefined -> mnesia_data_dir()
end.
mnesia_data_dir() ->
MnesiaDir = filename:join([ z_config:get(data_dir), atom_to_list(node()), "mnesia" ]),
case z_filelib:ensure_dir(MnesiaDir) of
ok ->
application:set_env(mnesia, dir, MnesiaDir),
{ok, MnesiaDir};
{error, Reason} ->
?LOG_ERROR(#{
text => <<"Could not create mnesia dir">>,
in => zotonic_core,
path => MnesiaDir,
result => error,
reason => Reason
}),
undefined
end.