1 -- Ignore the CFG_* variables
2 -- luacheck: ignore 113/CFG_CONFIGDIR 113/CFG_SOURCEDIR 113/CFG_DATADIR 113/CFG_PLUGINDIR
5 local prosody
= { events
= require
"util.events".new() };
6 local logger
= require
"util.logger";
7 local log = logger
.init("startup");
9 local config
= require
"core.configmanager";
10 local config_warnings
;
12 local dependencies
= require
"util.dependencies";
14 local original_logging_config
;
16 function startup
.read_config()
20 if arg
[1] == "--config" and arg
[2] then
21 table.insert(filenames
, arg
[2]);
23 table.insert(filenames
, CFG_CONFIGDIR
.."/"..arg
[2]);
25 table.remove(arg
, 1); table.remove(arg
, 1);
26 elseif os
.getenv("PROSODY_CONFIG") then -- Passed by prosodyctl
27 table.insert(filenames
, os
.getenv("PROSODY_CONFIG"));
29 table.insert(filenames
, (CFG_CONFIGDIR
or ".").."/prosody.cfg.lua");
31 for _
,_filename
in ipairs(filenames
) do
33 local file
= io
.open(filename
);
36 prosody
.config_file
= filename
;
37 CFG_CONFIGDIR
= filename
:match("^(.*)[\\/][^\\/]*$"); -- luacheck: ignore 111
41 prosody
.config_file
= filename
42 local ok
, level
, err
= config
.load(filename
);
45 print("**************************");
46 if level
== "parser" then
47 print("A problem occurred while reading the config file "..filename
);
49 local err_line
, err_message
= tostring(err
):match("%[string .-%]:(%d*): (.*)");
50 if err
:match("chunk has too many syntax levels$") then
51 print("An Include statement in a config file is including an already-included");
52 print("file and causing an infinite loop. An Include statement in a config file is...");
54 print("Error"..(err_line
and (" on line "..err_line
) or "")..": "..(err_message
or tostring(err
)));
57 elseif level
== "file" then
58 print("Prosody was unable to find the configuration file.");
59 print("We looked for: "..filename
);
60 print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
61 print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
63 print("More help on configuring Prosody can be found at https://prosody.im/doc/configure");
65 print("**************************");
68 elseif err
and #err
> 0 then
69 config_warnings
= err
;
71 prosody
.config_loaded
= true;
74 function startup
.check_dependencies()
75 if not dependencies
.check_dependencies() then
80 -- luacheck: globals socket server
82 function startup
.load_libraries()
83 -- Load socket framework
84 -- luacheck: ignore 111/server 111/socket
85 socket
= require
"socket";
86 server
= require
"net.server"
89 function startup
.init_logging()
91 local loggingmanager
= require
"core.loggingmanager"
92 loggingmanager
.reload_logging();
93 prosody
.events
.add_handler("config-reloaded", function ()
94 prosody
.events
.fire_event("reopen-log-files");
96 prosody
.events
.add_handler("reopen-log-files", function ()
97 loggingmanager
.reload_logging();
98 prosody
.events
.fire_event("logging-reloaded");
102 function startup
.log_startup_warnings()
103 dependencies
.log_warnings();
104 if config_warnings
then
105 for _
, warning
in ipairs(config_warnings
) do
106 log("warn", "Configuration warning: %s", warning
);
111 function startup
.sanity_check()
112 for host
, host_config
in pairs(config
.getconfig()) do
114 and host_config
.enabled
~= false
115 and not host_config
.component_module
then
119 log("error", "No enabled VirtualHost entries found in the config file.");
120 log("error", "At least one active host is required for Prosody to function. Exiting...");
124 function startup
.sandbox_require()
125 -- Replace require() with one that doesn't pollute _G, required
126 -- for neat sandboxing of modules
127 -- luacheck: ignore 113/getfenv 111/require
129 local _real_require
= require
;
130 local getfenv
= getfenv
or function (f
)
131 -- FIXME: This is a hack to replace getfenv() in Lua 5.2
132 local name
, env
= debug
.getupvalue(debug
.getinfo(f
or 1).func
, 1);
133 if name
== "_ENV" then
137 function require(...) -- luacheck: ignore 121
138 local curr_env
= getfenv(2);
139 local curr_env_mt
= getmetatable(curr_env
);
140 local _realG_mt
= getmetatable(_realG
);
141 if curr_env_mt
and curr_env_mt
.__index
and not curr_env_mt
.__newindex
and _realG_mt
then
142 local old_newindex
, old_index
;
143 old_newindex
, _realG_mt
.__newindex
= _realG_mt
.__newindex
, curr_env
;
144 old_index
, _realG_mt
.__index
= _realG_mt
.__index
, function (_G
, k
) -- luacheck: ignore 212/_G
145 return rawget(curr_env
, k
);
147 local ret
= _real_require(...);
148 _realG_mt
.__newindex
= old_newindex
;
149 _realG_mt
.__index
= old_index
;
152 return _real_require(...);
156 function startup
.set_function_metatable()
158 function mt
.__index(f
, upvalue
)
159 local i
, name
, value
= 0;
162 name
, value
= debug
.getupvalue(f
, i
);
163 until name
== upvalue
or name
== nil;
166 function mt
.__newindex(f
, upvalue
, value
)
170 name
= debug
.getupvalue(f
, i
);
171 until name
== upvalue
or name
== nil;
173 debug
.setupvalue(f
, i
, value
);
176 function mt
.__tostring(f
)
177 local info
= debug
.getinfo(f
);
178 return ("function(%s:%d)"):format(info
.short_src
:match("[^\\/]*$"), info
.linedefined
);
180 debug
.setmetatable(function() end, mt
);
183 function startup
.detect_platform()
184 prosody
.platform
= "unknown";
185 if os
.getenv("WINDIR") then
186 prosody
.platform
= "windows";
187 elseif package
.config
:sub(1,1) == "/" then
188 prosody
.platform
= "posix";
192 function startup
.detect_installed()
193 prosody
.installed
= nil;
194 if CFG_SOURCEDIR
and (prosody
.platform
== "windows" or CFG_SOURCEDIR
:match("^/")) then
195 prosody
.installed
= true;
199 function startup
.init_global_state()
200 -- luacheck: ignore 121
201 prosody
.bare_sessions
= {};
202 prosody
.full_sessions
= {};
205 -- COMPAT: These globals are deprecated
206 -- luacheck: ignore 111/bare_sessions 111/full_sessions 111/hosts
207 bare_sessions
= prosody
.bare_sessions
;
208 full_sessions
= prosody
.full_sessions
;
209 hosts
= prosody
.hosts
;
211 prosody
.paths
= { source
= CFG_SOURCEDIR
, config
= CFG_CONFIGDIR
or ".",
212 plugins
= CFG_PLUGINDIR
or "plugins", data
= "data" };
214 prosody
.arg
= _G
.arg
;
216 _G
.log = logger
.init("general");
217 prosody
.log = logger
.init("general");
219 startup
.detect_platform();
220 startup
.detect_installed();
221 _G
.prosody
= prosody
;
224 function startup
.setup_datadir()
225 prosody
.paths
.data
= config
.get("*", "data_path") or CFG_DATADIR
or "data";
228 function startup
.setup_plugindir()
229 local custom_plugin_paths
= config
.get("*", "plugin_paths");
230 local installer_plugin_path
= config
.get("*", "installer_plugin_path") or "custom_plugins";
231 local path_sep
= package
.config
:sub(3,3);
232 installer_plugin_path
= config
.resolve_relative_path(require
"lfs".currentdir(), installer_plugin_path
);
233 require
"lfs".mkdir(installer_plugin_path
);
234 require
"util.paths".complement_lua_path(installer_plugin_path
);
235 if custom_plugin_paths
then
236 -- path1;path2;path3;defaultpath...
237 -- luacheck: ignore 111
238 CFG_PLUGINDIR
= table.concat(custom_plugin_paths
, path_sep
)..path_sep
..(CFG_PLUGINDIR
or "plugins");
239 prosody
.paths
.plugins
= CFG_PLUGINDIR
;
241 CFG_PLUGINDIR
= installer_plugin_path
..path_sep
..(CFG_PLUGINDIR
or "plugins");
242 prosody
.paths
.plugins
= CFG_PLUGINDIR
;
245 function startup
.chdir()
246 if prosody
.installed
then
247 -- Change working directory to data path.
248 require
"lfs".chdir(prosody
.paths
.data
);
252 function startup
.add_global_prosody_functions()
253 -- Function to reload the config file
254 function prosody
.reload_config()
255 log("info", "Reloading configuration file");
256 prosody
.events
.fire_event("reloading-config");
257 local ok
, level
, err
= config
.load(prosody
.config_file
);
259 if level
== "parser" then
260 log("error", "There was an error parsing the configuration file: %s", err
);
261 elseif level
== "file" then
262 log("error", "Couldn't read the config file when trying to reload: %s", err
);
265 prosody
.events
.fire_event("config-reloaded", {
266 filename
= prosody
.config_file
,
267 config
= config
.getconfig(),
270 return ok
, (err
and tostring(level
)..": "..tostring(err
)) or nil;
273 -- Function to reopen logfiles
274 function prosody
.reopen_logfiles()
275 log("info", "Re-opening log files");
276 prosody
.events
.fire_event("reopen-log-files");
279 -- Function to initiate prosody shutdown
280 function prosody
.shutdown(reason
, code
)
281 log("info", "Shutting down: %s", reason
or "unknown reason");
282 prosody
.shutdown_reason
= reason
;
283 prosody
.shutdown_code
= code
;
284 prosody
.events
.fire_event("server-stopping", {
288 server
.setquitting(true);
292 function startup
.load_secondary_libraries()
293 --- Load and initialise core modules
294 require
"util.import"
295 require
"util.xmppstream"
296 require
"core.stanza_router"
297 require
"core.statsmanager"
298 require
"core.hostmanager"
299 require
"core.portmanager"
300 require
"core.modulemanager"
301 require
"core.usermanager"
302 require
"core.rostermanager"
303 require
"core.sessionmanager"
304 package
.loaded
['core.componentmanager'] = setmetatable({},{__index
=function()
305 -- COMPAT which version?
306 log("warn", "componentmanager is deprecated: %s", debug
.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)"));
307 return function() end
311 require
"util.datetime"
312 require
"util.iterators"
314 require
"util.helpers"
316 pcall(require
, "util.signal") -- Not on Windows
318 -- Commented to protect us from
319 -- the second kind of people
321 pcall(require, "remdebug.engine");
322 if remdebug then remdebug.engine.start() end
325 require
"util.stanza"
329 function startup
.init_http_client()
330 local http
= require
"net.http"
331 local config_ssl
= config
.get("*", "ssl") or {}
332 local https_client
= config
.get("*", "client_https_ssl")
333 http
.default
.options
.sslctx
= require
"core.certmanager".create_context("client_https port 0", "client",
334 { capath
= config_ssl
.capath
, cafile
= config_ssl
.cafile
, verify
= "peer", }, https_client
);
337 function startup
.init_data_store()
338 require
"core.storagemanager";
341 function startup
.prepare_to_start()
342 log("info", "Prosody is using the %s backend for connection handling", server
.get_backend());
343 -- Signal to modules that we are ready to start
344 prosody
.events
.fire_event("server-starting");
345 prosody
.start_time
= os
.time();
348 function startup
.init_global_protection()
349 -- Catch global accesses
350 -- luacheck: ignore 212/t
351 local locked_globals_mt
= {
352 __index
= function (t
, k
) log("warn", "%s", debug
.traceback("Attempt to read a non-existent global '"..tostring(k
).."'", 2)); end;
353 __newindex
= function (t
, k
, v
) error("Attempt to set a global: "..tostring(k
).." = "..tostring(v
), 2); end;
356 function prosody
.unlock_globals()
357 setmetatable(_G
, nil);
360 function prosody
.lock_globals()
361 setmetatable(_G
, locked_globals_mt
);
365 prosody
.lock_globals();
368 function startup
.read_version()
369 -- Try to determine version
370 local version_file
= io
.open((CFG_SOURCEDIR
or ".").."/prosody.version");
371 prosody
.version
= "unknown";
373 prosody
.version
= version_file
:read("*a"):gsub("%s*$", "");
374 version_file
:close();
375 if #prosody
.version
== 12 and prosody
.version
:match("^[a-f0-9]+$") then
376 prosody
.version
= "hg:"..prosody
.version
;
379 local hg
= require
"util.mercurial";
380 local hgid
= hg
.check_id(CFG_SOURCEDIR
or ".");
381 if hgid
then prosody
.version
= "hg:" .. hgid
; end
385 function startup
.log_greeting()
386 log("info", "Hello and welcome to Prosody version %s", prosody
.version
);
389 function startup
.notify_started()
390 prosody
.events
.fire_event("server-started");
393 -- Override logging config (used by prosodyctl)
394 function startup
.force_console_logging()
395 original_logging_config
= config
.get("*", "log");
396 config
.set("*", "log", { { levels
= { min = os
.getenv("PROSODYCTL_LOG_LEVEL") or "info" }, to
= "console" } });
399 function startup
.switch_user()
400 -- Switch away from root and into the prosody user --
401 -- NOTE: This function is only used by prosodyctl.
402 -- The prosody process is built with the assumption that
403 -- it is already started as the appropriate user.
405 local want_pposix_version
= "0.4.0";
406 local have_pposix
, pposix
= pcall(require
, "util.pposix");
408 if have_pposix
and pposix
then
409 if pposix
._VERSION
~= want_pposix_version
then
410 print(string.format("Unknown version (%s) of binary pposix module, expected %s",
411 tostring(pposix
._VERSION
), want_pposix_version
));
414 prosody
.current_uid
= pposix
.getuid();
415 local arg_root
= arg
[1] == "--root";
416 if arg_root
then table.remove(arg
, 1); end
417 if prosody
.current_uid
== 0 and config
.get("*", "run_as_root") ~= true and not arg_root
then
419 local desired_user
= config
.get("*", "prosody_user") or "prosody";
420 local desired_group
= config
.get("*", "prosody_group") or desired_user
;
421 local ok
, err
= pposix
.setgid(desired_group
);
423 ok
, err
= pposix
.initgroups(desired_user
);
426 ok
, err
= pposix
.setuid(desired_user
);
429 prosody
.switched_user
= true;
432 if not prosody
.switched_user
then
434 print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user
).."'/'"..tostring(desired_group
).."': "..tostring(err
));
436 -- Make sure the Prosody user can read the config
437 local conf
, err
, errno
= io
.open(prosody
.config_file
);
441 print("The config file is not readable by the '"..desired_user
.."' user.");
442 print("Prosody will not be able to read it.");
443 print("Error was "..err
);
449 -- Set our umask to protect data files
450 pposix
.umask(config
.get("*", "umask") or "027");
451 pposix
.setenv("HOME", prosody
.paths
.data
);
452 pposix
.setenv("PROSODY_CONFIG", prosody
.config_file
);
454 print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
455 print("For more help send the below error to us through https://prosody.im/discuss");
456 print(tostring(pposix
))
461 function startup
.check_unwriteable()
462 local function test_writeable(filename
)
463 local f
, err
= io
.open(filename
, "a");
471 local unwriteable_files
= {};
472 if type(original_logging_config
) == "string" and original_logging_config
:sub(1,1) ~= "*" then
473 local ok
, err
= test_writeable(original_logging_config
);
475 table.insert(unwriteable_files
, err
);
477 elseif type(original_logging_config
) == "table" then
478 for _
, rule
in ipairs(original_logging_config
) do
479 if rule
.filename
then
480 local ok
, err
= test_writeable(rule
.filename
);
482 table.insert(unwriteable_files
, err
);
488 if #unwriteable_files
> 0 then
489 print("One of more of the Prosody log files are not");
490 print("writeable, please correct the errors and try");
491 print("starting prosodyctl again.");
493 for _
, err
in ipairs(unwriteable_files
) do
501 function startup
.make_host(hostname
)
504 events
= prosody
.events
,
507 users
= require
"core.usermanager".new_null_provider(hostname
)
511 function startup
.make_dummy_hosts()
512 -- When running under prosodyctl, we don't want to
513 -- fully initialize the server, so we populate prosody.hosts
514 -- with just enough things for most code to work correctly
515 -- luacheck: ignore 122/hosts
516 prosody
.core_post_stanza
= function () end; -- TODO: mod_router!
518 for hostname
in pairs(config
.getconfig()) do
519 prosody
.hosts
[hostname
] = startup
.make_host(hostname
);
524 function startup
.prosodyctl()
525 startup
.init_global_state();
526 startup
.read_config();
527 startup
.force_console_logging();
528 startup
.init_logging();
529 startup
.setup_plugindir();
530 startup
.setup_datadir();
532 startup
.read_version();
533 startup
.switch_user();
534 startup
.check_dependencies();
535 startup
.log_startup_warnings();
536 startup
.check_unwriteable();
537 startup
.load_libraries();
538 startup
.init_http_client();
539 startup
.make_dummy_hosts();
542 function startup
.prosody()
543 -- These actions are in a strict order, as many depend on
544 -- previous steps to have already been performed
545 startup
.init_global_state();
546 startup
.read_config();
547 startup
.init_logging();
548 startup
.sanity_check();
549 startup
.sandbox_require();
550 startup
.set_function_metatable();
551 startup
.check_dependencies();
552 startup
.init_logging();
553 startup
.load_libraries();
554 startup
.setup_plugindir();
555 startup
.setup_datadir();
557 startup
.add_global_prosody_functions();
558 startup
.read_version();
559 startup
.log_greeting();
560 startup
.log_startup_warnings();
561 startup
.load_secondary_libraries();
562 startup
.init_http_client();
563 startup
.init_data_store();
564 startup
.init_global_protection();
565 startup
.prepare_to_start();
566 startup
.notify_started();