diff options
Diffstat (limited to 'util/startup.lua')
-rw-r--r-- | util/startup.lua | 336 |
1 files changed, 278 insertions, 58 deletions
diff --git a/util/startup.lua b/util/startup.lua index 545b6ae7..caae895d 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -2,15 +2,15 @@ -- luacheck: ignore 113/CFG_CONFIGDIR 113/CFG_SOURCEDIR 113/CFG_DATADIR 113/CFG_PLUGINDIR local startup = {}; -local prosody = { events = require "util.events".new() }; -local logger = require "util.logger"; +local prosody = { events = require "prosody.util.events".new() }; +local logger = require "prosody.util.logger"; local log = logger.init("startup"); -local parse_args = require "util.argparse".parse; +local parse_args = require "prosody.util.argparse".parse; -local config = require "core.configmanager"; +local config = require "prosody.core.configmanager"; local config_warnings; -local dependencies = require "util.dependencies"; +local dependencies = require "prosody.util.dependencies"; local original_logging_config; @@ -132,14 +132,14 @@ end function startup.load_libraries() -- Load socket framework -- luacheck: ignore 111/server 111/socket - require "util.import" + require "prosody.util.import" socket = require "socket"; - server = require "net.server" + server = require "prosody.net.server" end function startup.init_logging() -- Initialize logging - local loggingmanager = require "core.loggingmanager" + local loggingmanager = require "prosody.core.loggingmanager" loggingmanager.reload_logging(); prosody.events.add_handler("config-reloaded", function () prosody.events.fire_event("reopen-log-files"); @@ -233,7 +233,7 @@ function startup.set_function_metatable() if info.isvararg then info[n_params+1] = "..."; end - return ("function<%s:%d>(%s)"):format(info.short_src:match("[^\\/]*$"), info.linedefined, table.concat(info, ", ")); + return ("function @%s:%d(%s)"):format(info.short_src:match("[^\\/]*$"), info.linedefined, table.concat(info, ", ")); end debug.setmetatable(function() end, mt); end @@ -277,6 +277,11 @@ function startup.init_global_state() startup.detect_platform(); startup.detect_installed(); _G.prosody = prosody; + + -- COMPAT Lua < 5.3 + if not math.type then + require "prosody.util.mathcompat" + end end function startup.setup_datadir() @@ -298,7 +303,7 @@ function startup.setup_plugin_install_path() local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; local path_sep = package.config:sub(3,3); installer_plugin_path = config.resolve_relative_path(CFG_DATADIR or "data", installer_plugin_path); - require"util.paths".complement_lua_path(installer_plugin_path); + require"prosody.util.paths".complement_lua_path(installer_plugin_path); -- luacheck: ignore 111 CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.installer = installer_plugin_path; @@ -360,28 +365,23 @@ end function startup.load_secondary_libraries() --- Load and initialise core modules - require "util.xmppstream" - require "core.stanza_router" - require "core.statsmanager" - require "core.hostmanager" - require "core.portmanager" - require "core.modulemanager" - require "core.usermanager" - require "core.rostermanager" - require "core.sessionmanager" - package.loaded['core.componentmanager'] = setmetatable({},{__index=function() - -- COMPAT which version? - log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)")); - return function() end - end}); - - require "util.array" - require "util.datetime" - require "util.iterators" - require "util.timer" - require "util.helpers" - - pcall(require, "util.signal") -- Not on Windows + require "prosody.util.xmppstream" + require "prosody.core.stanza_router" + require "prosody.core.statsmanager".metric("gauge", "prosody_info", "", "Prosody version", { "version" }):with_labels(prosody.version):set(1); + require "prosody.core.hostmanager" + require "prosody.core.portmanager" + require "prosody.core.modulemanager" + require "prosody.core.usermanager" + require "prosody.core.rostermanager" + require "prosody.core.sessionmanager" + + require "prosody.util.array" + require "prosody.util.datetime" + require "prosody.util.iterators" + require "prosody.util.timer" + require "prosody.util.helpers" + + pcall(require, "prosody.util.signal") -- Not on Windows -- Commented to protect us from -- the second kind of people @@ -390,43 +390,83 @@ function startup.load_secondary_libraries() if remdebug then remdebug.engine.start() end ]] - require "util.stanza" - require "util.jid" + require "prosody.util.stanza" + require "prosody.util.jid" + + prosody.features = require "prosody.core.features".available; end function startup.init_http_client() - local http = require "net.http" + local http = require "prosody.net.http" local config_ssl = config.get("*", "ssl") or {} local https_client = config.get("*", "client_https_ssl") - http.default.options.sslctx = require "core.certmanager".create_context("client_https port 0", "client", + http.default.options.sslctx = require "prosody.core.certmanager".create_context("client_https port 0", "client", { capath = config_ssl.capath, cafile = config_ssl.cafile, verify = "peer", }, https_client); http.default.options.use_dane = config.get("*", "use_dane") end function startup.init_promise() - local promise = require "util.promise"; + local promise = require "prosody.util.promise"; - local timer = require "util.timer"; + local timer = require "prosody.util.timer"; promise.set_nexttick(function(f) return timer.add_task(0, f); end); end function startup.init_async() - local async = require "util.async"; + local async = require "prosody.util.async"; - local timer = require "util.timer"; + local timer = require "prosody.util.timer"; async.set_nexttick(function(f) return timer.add_task(0, f); end); async.set_schedule_function(timer.add_task); end function startup.init_data_store() - require "core.storagemanager"; + require "prosody.core.storagemanager"; end +local running_state = require "prosody.util.fsm".new({ + default_state = "uninitialized"; + transitions = { + { name = "begin_startup", from = "uninitialized", to = "starting" }; + { name = "finish_startup", from = "starting", to = "running" }; + { name = "begin_shutdown", from = { "running", "starting" }, to = "stopping" }; + { name = "finish_shutdown", from = "stopping", to = "stopped" }; + }; + handlers = { + transitioned = function (transition) + prosody.state = transition.to; + end; + }; + state_handlers = { + starting = function () + prosody.log("debug", "Firing server-starting event"); + prosody.events.fire_event("server-starting"); + prosody.start_time = os.time(); + end; + running = function () + prosody.log("debug", "Startup complete, firing server-started"); + prosody.events.fire_event("server-started"); + end; + }; +}):init(); + function startup.prepare_to_start() log("info", "Prosody is using the %s backend for connection handling", server.get_backend()); + -- Signal to modules that we are ready to start - prosody.events.fire_event("server-starting"); - prosody.start_time = os.time(); + prosody.started = require "prosody.util.promise".new(function (resolve) + if prosody.state == "running" then + resolve(); + else + prosody.events.add_handler("server-started", function () + resolve(); + end); + end + end):catch(function (err) + prosody.log("error", "Prosody startup error: %s", err); + end); + + running_state:begin_startup(); end function startup.init_global_protection() @@ -460,7 +500,7 @@ function startup.read_version() prosody.version = "hg:"..prosody.version; end else - local hg = require"util.mercurial"; + local hg = require"prosody.util.mercurial"; local hgid = hg.check_id(CFG_SOURCEDIR or "."); if hgid then prosody.version = "hg:" .. hgid; end end @@ -471,7 +511,7 @@ function startup.log_greeting() end function startup.notify_started() - prosody.events.fire_event("server-started"); + running_state:finish_startup(); end -- Override logging config (used by prosodyctl) @@ -491,21 +531,30 @@ function startup.force_console_logging() config.set("*", "log", { { levels = { min = log_level or "info" }, to = "console" } }); end +local function check_posix() + if prosody.platform ~= "posix" then return end + + local want_pposix_version = "0.4.1"; + local have_pposix, pposix = pcall(require, "prosody.util.pposix"); + + if pposix._VERSION ~= want_pposix_version then + print(string.format("Unknown version (%s) of binary pposix module, expected %s", + tostring(pposix._VERSION), want_pposix_version)); + os.exit(1); + end + if have_pposix and pposix then + return pposix; + end +end + function startup.switch_user() -- Switch away from root and into the prosody user -- -- NOTE: This function is only used by prosodyctl. -- The prosody process is built with the assumption that -- it is already started as the appropriate user. - local want_pposix_version = "0.4.0"; - local have_pposix, pposix = pcall(require, "util.pposix"); - - if have_pposix and pposix then - if pposix._VERSION ~= want_pposix_version then - print(string.format("Unknown version (%s) of binary pposix module, expected %s", - tostring(pposix._VERSION), want_pposix_version)); - os.exit(1); - end + local pposix = check_posix() + if pposix then prosody.current_uid = pposix.getuid(); local arg_root = prosody.opts.root; if prosody.current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then @@ -594,7 +643,7 @@ end function startup.init_gc() -- Apply garbage collector settings from the config file - local gc = require "util.gc"; + local gc = require "prosody.util.gc"; local gc_settings = config.get("*", "gc") or { mode = default_gc_params.mode }; local ok, err = gc.configure(gc_settings, default_gc_params); @@ -606,7 +655,7 @@ function startup.init_gc() end function startup.init_errors() - require "util.error".configure(config.get("*", "error_library") or {}); + require "prosody.util.error".configure(config.get("*", "error_library") or {}); end function startup.make_host(hostname) @@ -615,7 +664,7 @@ function startup.make_host(hostname) events = prosody.events, modules = {}, sessions = {}, - users = require "core.usermanager".new_null_provider(hostname) + users = require "prosody.core.usermanager".new_null_provider(hostname) }; end @@ -631,17 +680,183 @@ function startup.make_dummy_hosts() end end +function startup.posix_umask() + if prosody.platform ~= "posix" then return end + local pposix = require "prosody.util.pposix"; + local umask = config.get("*", "umask") or "027"; + pposix.umask(umask); +end + +function startup.check_user() + local pposix = check_posix(); + if not pposix then return end + -- Don't even think about it! + if pposix.getuid() == 0 and not config.get("*", "run_as_root") then + print("Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!"); + print("For more information on running Prosody as root, see https://prosody.im/doc/root"); + os.exit(1); -- Refusing to run as root + end +end + +local function remove_pidfile() + local pidfile = prosody.pidfile; + if prosody.pidfile_handle then + prosody.pidfile_handle:close(); + os.remove(pidfile); + prosody.pidfile, prosody.pidfile_handle = nil, nil; + end +end + +function startup.write_pidfile() + local pposix = check_posix(); + if not pposix then return end + local lfs = require "lfs"; + local stat = lfs.attributes; + local pidfile = config.get("*", "pidfile") or nil; + if not pidfile then return end + pidfile = config.resolve_relative_path(prosody.paths.data, pidfile); + local mode = stat(pidfile) and "r+" or "w+"; + local pidfile_handle, err = io.open(pidfile, mode); + if not pidfile_handle then + log("error", "Couldn't write pidfile at %s; %s", pidfile, err); + os.exit(1); + else + prosody.pidfile = pidfile; + if not lfs.lock(pidfile_handle, "w") then -- Exclusive lock + local other_pid = pidfile_handle:read("*a"); + log("error", "Another Prosody instance seems to be running with PID %s, quitting", other_pid); + prosody.pidfile_handle = nil; + os.exit(1); + else + pidfile_handle:close(); + pidfile_handle, err = io.open(pidfile, "w+"); + if not pidfile_handle then + log("error", "Couldn't write pidfile at %s; %s", pidfile, err); + os.exit(1); + else + if lfs.lock(pidfile_handle, "w") then + pidfile_handle:write(tostring(pposix.getpid())); + pidfile_handle:flush(); + prosody.pidfile_handle = pidfile_handle; + end + end + end + end + prosody.events.add_handler("server-stopped", remove_pidfile); +end + +local function remove_log_sinks() + local lm = require "prosody.core.loggingmanager"; + lm.register_sink_type("console", nil); + lm.register_sink_type("stdout", nil); + lm.reload_logging(); +end + +function startup.posix_daemonize() + if not prosody.opts.daemonize then return end + local pposix = check_posix(); + log("info", "Prosody is about to detach from the console, disabling further console output"); + remove_log_sinks(); + local ok, ret = pposix.daemonize(); + if not ok then + log("error", "Failed to daemonize: %s", ret); + elseif ret and ret > 0 then + os.exit(0); + else + log("info", "Successfully daemonized to PID %d", pposix.getpid()); + end +end + +function startup.hook_posix_signals() + if prosody.platform ~= "posix" then return end + local have_signal, signal = pcall(require, "prosody.util.signal"); + if not have_signal then + log("warn", "Couldn't load signal library, won't respond to SIGTERM"); + return + end + signal.signal("SIGTERM", function() + log("warn", "Received SIGTERM"); + prosody.main_thread:run(function() + prosody.unlock_globals(); + prosody.shutdown("Received SIGTERM"); + prosody.lock_globals(); + end); + end); + + signal.signal("SIGHUP", function() + log("info", "Received SIGHUP"); + prosody.main_thread:run(function() prosody.reload_config(); end); + -- this also reloads logging + end); + + signal.signal("SIGINT", function() + log("info", "Received SIGINT"); + prosody.main_thread:run(function() + prosody.unlock_globals(); + prosody.shutdown("Received SIGINT"); + prosody.lock_globals(); + end); + end); + + signal.signal("SIGUSR1", function() + log("info", "Received SIGUSR1"); + prosody.events.fire_event("signal/SIGUSR1"); + end); + + signal.signal("SIGUSR2", function() + log("info", "Received SIGUSR2"); + prosody.events.fire_event("signal/SIGUSR2"); + end); +end + +function startup.systemd_notify() + local notify_socket_name = os.getenv("NOTIFY_SOCKET"); + if not notify_socket_name then return end + local have_unix, unix = pcall(require, "socket.unix"); + if not have_unix or type(unix) ~= "table" then + log("error", "LuaSocket without UNIX socket support, can't notify systemd.") + return os.exit(1); + end + log("debug", "Will notify on socket %q", notify_socket_name); + notify_socket_name = notify_socket_name:gsub("^@", "\0"); + local notify_socket = unix.dgram(); + local ok, err = notify_socket:setpeername(notify_socket_name); + if not ok then + log("error", "Could not connect to systemd notification socket %q: %q", notify_socket_name, err); + return os.exit(1); + end + local time = require "prosody.util.time"; + + prosody.notify_socket = notify_socket; + prosody.events.add_handler("server-started", function() + notify_socket:send("READY=1"); + end); + prosody.events.add_handler("reloading-config", function() + notify_socket:send(string.format("RELOADING=1\nMONOTONIC_USEC=%d", math.floor(time.monotonic() * 1000000))); + end); + prosody.events.add_handler("config-reloaded", function() + notify_socket:send("READY=1"); + end); + prosody.events.add_handler("server-stopping", function() + notify_socket:send("STOPPING=1"); + end); +end + function startup.cleanup() prosody.log("info", "Shutdown status: Cleaning up"); prosody.events.fire_event("server-cleanup"); end function startup.shutdown() + running_state:begin_shutdown(); + prosody.log("info", "Shutting down..."); startup.cleanup(); prosody.events.fire_event("server-stopped"); - prosody.log("info", "Shutdown complete"); + running_state:finish_shutdown(); + + prosody.log("info", "Shutdown complete"); prosody.log("debug", "Shutdown reason was: %s", prosody.shutdown_reason or "not specified"); prosody.log("debug", "Exiting with status code: %d", prosody.shutdown_code or 0); server.setquitting(true); @@ -682,6 +897,7 @@ function startup.prosody() startup.parse_args(); startup.init_global_state(); startup.read_config(); + startup.check_user(); startup.init_logging(); startup.init_gc(); startup.init_errors(); @@ -704,6 +920,10 @@ function startup.prosody() startup.init_http_client(); startup.init_data_store(); startup.init_global_protection(); + startup.posix_daemonize(); + startup.write_pidfile(); + startup.hook_posix_signals(); + startup.systemd_notify(); startup.prepare_to_start(); startup.notify_started(); end |