Software /
code /
prosody
Comparison
util/startup.lua @ 8635:47e3b8b6f17a
prosody, prosodyctl, util.startup: Finally factor out startup-related and common code into a separate module
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Tue, 20 Mar 2018 16:10:37 +0000 |
child | 8636:8691083420e4 |
comparison
equal
deleted
inserted
replaced
8634:f6f62c92b642 | 8635:47e3b8b6f17a |
---|---|
1 local startup = {}; | |
2 | |
3 local prosody = { events = require "util.events".new() }; | |
4 | |
5 local config = require "core.configmanager"; | |
6 | |
7 local dependencies = require "util.dependencies"; | |
8 | |
9 function startup.read_config() | |
10 local filenames = {}; | |
11 | |
12 local filename; | |
13 if arg[1] == "--config" and arg[2] then | |
14 table.insert(filenames, arg[2]); | |
15 if CFG_CONFIGDIR then | |
16 table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]); | |
17 end | |
18 table.remove(arg, 1); table.remove(arg, 1); | |
19 elseif os.getenv("PROSODY_CONFIG") then -- Passed by prosodyctl | |
20 table.insert(filenames, os.getenv("PROSODY_CONFIG")); | |
21 else | |
22 table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua"); | |
23 end | |
24 for _,_filename in ipairs(filenames) do | |
25 filename = _filename; | |
26 local file = io.open(filename); | |
27 if file then | |
28 file:close(); | |
29 prosody.config_file = filename; | |
30 CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$"); | |
31 break; | |
32 end | |
33 end | |
34 prosody.config_file = filename | |
35 local ok, level, err = config.load(filename); | |
36 if not ok then | |
37 print("\n"); | |
38 print("**************************"); | |
39 if level == "parser" then | |
40 print("A problem occured while reading the config file "..filename); | |
41 print(""); | |
42 local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)"); | |
43 if err:match("chunk has too many syntax levels$") then | |
44 print("An Include statement in a config file is including an already-included"); | |
45 print("file and causing an infinite loop. An Include statement in a config file is..."); | |
46 else | |
47 print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err))); | |
48 end | |
49 print(""); | |
50 elseif level == "file" then | |
51 print("Prosody was unable to find the configuration file."); | |
52 print("We looked for: "..filename); | |
53 print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist"); | |
54 print("Copy or rename it to prosody.cfg.lua and edit as necessary."); | |
55 end | |
56 print("More help on configuring Prosody can be found at https://prosody.im/doc/configure"); | |
57 print("Good luck!"); | |
58 print("**************************"); | |
59 print(""); | |
60 os.exit(1); | |
61 end | |
62 end | |
63 | |
64 function startup.check_dependencies() | |
65 if not dependencies.check_dependencies() then | |
66 os.exit(1); | |
67 end | |
68 end | |
69 | |
70 -- luacheck: globals socket server | |
71 | |
72 function startup.load_libraries() | |
73 -- Load socket framework | |
74 -- luacheck: ignore 111/server 111/socket | |
75 socket = require "socket"; | |
76 server = require "net.server" | |
77 end | |
78 | |
79 -- The global log() gets defined by loggingmanager | |
80 -- luacheck: ignore 113/log | |
81 | |
82 function startup.init_logging() | |
83 -- Initialize logging | |
84 require "core.loggingmanager" | |
85 end | |
86 | |
87 function startup.log_dependency_warnings() | |
88 dependencies.log_warnings(); | |
89 end | |
90 | |
91 function startup.sanity_check() | |
92 for host, host_config in pairs(config.getconfig()) do | |
93 if host ~= "*" | |
94 and host_config.enabled ~= false | |
95 and not host_config.component_module then | |
96 return; | |
97 end | |
98 end | |
99 log("error", "No enabled VirtualHost entries found in the config file."); | |
100 log("error", "At least one active host is required for Prosody to function. Exiting..."); | |
101 os.exit(1); | |
102 end | |
103 | |
104 function startup.sandbox_require() | |
105 -- Replace require() with one that doesn't pollute _G, required | |
106 -- for neat sandboxing of modules | |
107 -- luacheck: ignore 113/getfenv 111/require | |
108 local _realG = _G; | |
109 local _real_require = require; | |
110 local getfenv = getfenv or function (f) | |
111 -- FIXME: This is a hack to replace getfenv() in Lua 5.2 | |
112 local name, env = debug.getupvalue(debug.getinfo(f or 1).func, 1); | |
113 if name == "_ENV" then | |
114 return env; | |
115 end | |
116 end | |
117 function require(...) | |
118 local curr_env = getfenv(2); | |
119 local curr_env_mt = getmetatable(curr_env); | |
120 local _realG_mt = getmetatable(_realG); | |
121 if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then | |
122 local old_newindex, old_index; | |
123 old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env; | |
124 old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k) -- luacheck: ignore 212/_G | |
125 return rawget(curr_env, k); | |
126 end; | |
127 local ret = _real_require(...); | |
128 _realG_mt.__newindex = old_newindex; | |
129 _realG_mt.__index = old_index; | |
130 return ret; | |
131 end | |
132 return _real_require(...); | |
133 end | |
134 end | |
135 | |
136 function startup.set_function_metatable() | |
137 local mt = {}; | |
138 function mt.__index(f, upvalue) | |
139 local i, name, value = 0; | |
140 repeat | |
141 i = i + 1; | |
142 name, value = debug.getupvalue(f, i); | |
143 until name == upvalue or name == nil; | |
144 return value; | |
145 end | |
146 function mt.__newindex(f, upvalue, value) | |
147 local i, name = 0; | |
148 repeat | |
149 i = i + 1; | |
150 name = debug.getupvalue(f, i); | |
151 until name == upvalue or name == nil; | |
152 if name then | |
153 debug.setupvalue(f, i, value); | |
154 end | |
155 end | |
156 function mt.__tostring(f) | |
157 local info = debug.getinfo(f); | |
158 return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined); | |
159 end | |
160 debug.setmetatable(function() end, mt); | |
161 end | |
162 | |
163 function startup.detect_platform() | |
164 prosody.platform = "unknown"; | |
165 if os.getenv("WINDIR") then | |
166 prosody.platform = "windows"; | |
167 elseif package.config:sub(1,1) == "/" then | |
168 prosody.platform = "posix"; | |
169 end | |
170 end | |
171 | |
172 function startup.detect_installed() | |
173 prosody.installed = nil; | |
174 if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then | |
175 prosody.installed = true; | |
176 end | |
177 end | |
178 | |
179 function startup.chdir() | |
180 if prosody.installed then | |
181 -- Change working directory to data path. | |
182 require "lfs".chdir(data_path); | |
183 end | |
184 end | |
185 | |
186 function startup.init_global_state() | |
187 prosody.bare_sessions = {}; | |
188 prosody.full_sessions = {}; | |
189 prosody.hosts = {}; | |
190 | |
191 -- COMPAT: These globals are deprecated | |
192 -- luacheck: ignore 111/bare_sessions 111/full_sessions 111/hosts | |
193 bare_sessions = prosody.bare_sessions; | |
194 full_sessions = prosody.full_sessions; | |
195 hosts = prosody.hosts; | |
196 | |
197 local data_path = config.get("*", "data_path") or CFG_DATADIR or "data"; | |
198 local custom_plugin_paths = config.get("*", "plugin_paths"); | |
199 if custom_plugin_paths then | |
200 local path_sep = package.config:sub(3,3); | |
201 -- path1;path2;path3;defaultpath... | |
202 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); | |
203 end | |
204 prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".", | |
205 plugins = CFG_PLUGINDIR or "plugins", data = data_path }; | |
206 | |
207 prosody.arg = _G.arg; | |
208 | |
209 startup.detect_platform(); | |
210 startup.detect_installed(); | |
211 _G.prosody = prosody; | |
212 end | |
213 | |
214 function startup.add_global_prosody_functions() | |
215 -- Function to reload the config file | |
216 function prosody.reload_config() | |
217 log("info", "Reloading configuration file"); | |
218 prosody.events.fire_event("reloading-config"); | |
219 local ok, level, err = config.load(prosody.config_file); | |
220 if not ok then | |
221 if level == "parser" then | |
222 log("error", "There was an error parsing the configuration file: %s", tostring(err)); | |
223 elseif level == "file" then | |
224 log("error", "Couldn't read the config file when trying to reload: %s", tostring(err)); | |
225 end | |
226 end | |
227 return ok, (err and tostring(level)..": "..tostring(err)) or nil; | |
228 end | |
229 | |
230 -- Function to reopen logfiles | |
231 function prosody.reopen_logfiles() | |
232 log("info", "Re-opening log files"); | |
233 prosody.events.fire_event("reopen-log-files"); | |
234 end | |
235 | |
236 -- Function to initiate prosody shutdown | |
237 function prosody.shutdown(reason, code) | |
238 log("info", "Shutting down: %s", reason or "unknown reason"); | |
239 prosody.shutdown_reason = reason; | |
240 prosody.shutdown_code = code; | |
241 prosody.events.fire_event("server-stopping", { | |
242 reason = reason; | |
243 code = code; | |
244 }); | |
245 server.setquitting(true); | |
246 end | |
247 end | |
248 | |
249 function startup.load_secondary_libraries() | |
250 --- Load and initialise core modules | |
251 require "util.import" | |
252 require "util.xmppstream" | |
253 require "core.stanza_router" | |
254 require "core.statsmanager" | |
255 require "core.hostmanager" | |
256 require "core.portmanager" | |
257 require "core.modulemanager" | |
258 require "core.usermanager" | |
259 require "core.rostermanager" | |
260 require "core.sessionmanager" | |
261 package.loaded['core.componentmanager'] = setmetatable({},{__index=function() | |
262 log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)")); | |
263 return function() end | |
264 end}); | |
265 | |
266 require "util.array" | |
267 require "util.datetime" | |
268 require "util.iterators" | |
269 require "util.timer" | |
270 require "util.helpers" | |
271 | |
272 pcall(require, "util.signal") -- Not on Windows | |
273 | |
274 -- Commented to protect us from | |
275 -- the second kind of people | |
276 --[[ | |
277 pcall(require, "remdebug.engine"); | |
278 if remdebug then remdebug.engine.start() end | |
279 ]] | |
280 | |
281 require "util.stanza" | |
282 require "util.jid" | |
283 end | |
284 | |
285 function startup.init_http_client() | |
286 local http = require "net.http" | |
287 local config_ssl = config.get("*", "ssl") or {} | |
288 local https_client = config.get("*", "client_https_ssl") | |
289 http.default.options.sslctx = require "core.certmanager".create_context("client_https port 0", "client", | |
290 { capath = config_ssl.capath, cafile = config_ssl.cafile, verify = "peer", }, https_client); | |
291 end | |
292 | |
293 function startup.init_data_store() | |
294 require "core.storagemanager"; | |
295 end | |
296 | |
297 function startup.prepare_to_start() | |
298 log("info", "Prosody is using the %s backend for connection handling", server.get_backend()); | |
299 -- Signal to modules that we are ready to start | |
300 prosody.events.fire_event("server-starting"); | |
301 prosody.start_time = os.time(); | |
302 end | |
303 | |
304 function startup.init_global_protection() | |
305 -- Catch global accesses | |
306 -- luacheck: ignore 212/t | |
307 local locked_globals_mt = { | |
308 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end; | |
309 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end; | |
310 }; | |
311 | |
312 function prosody.unlock_globals() | |
313 setmetatable(_G, nil); | |
314 end | |
315 | |
316 function prosody.lock_globals() | |
317 setmetatable(_G, locked_globals_mt); | |
318 end | |
319 | |
320 -- And lock now... | |
321 prosody.lock_globals(); | |
322 end | |
323 | |
324 function startup.read_version() | |
325 -- Try to determine version | |
326 local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version"); | |
327 prosody.version = "unknown"; | |
328 if version_file then | |
329 prosody.version = version_file:read("*a"):gsub("%s*$", ""); | |
330 version_file:close(); | |
331 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then | |
332 prosody.version = "hg:"..prosody.version; | |
333 end | |
334 else | |
335 local hg = require"util.mercurial"; | |
336 local hgid = hg.check_id(CFG_SOURCEDIR or "."); | |
337 if hgid then prosody.version = "hg:" .. hgid; end | |
338 end | |
339 end | |
340 | |
341 function startup.log_greeting() | |
342 log("info", "Hello and welcome to Prosody version %s", prosody.version); | |
343 end | |
344 | |
345 function startup.notify_started() | |
346 prosody.events.fire_event("server-started"); | |
347 end | |
348 | |
349 -- Override logging config (used by prosodyctl) | |
350 function startup.force_console_logging() | |
351 local original_logging_config = config.get("*", "log"); | |
352 config.set("*", "log", { { levels = { min="info" }, to = "console" } }); | |
353 end | |
354 | |
355 function startup.switch_user() | |
356 -- Switch away from root and into the prosody user -- | |
357 -- NOTE: This function is only used by prosodyctl. | |
358 -- The prosody process is built with the assumption that | |
359 -- it is already started as the appropriate user. | |
360 local switched_user, current_uid; | |
361 | |
362 local want_pposix_version = "0.4.0"; | |
363 local have_pposix, pposix = pcall(require, "util.pposix"); | |
364 | |
365 if have_pposix and pposix then | |
366 if pposix._VERSION ~= want_pposix_version then | |
367 print(string.format("Unknown version (%s) of binary pposix module, expected %s", | |
368 tostring(pposix._VERSION), want_pposix_version)); | |
369 os.exit(1); | |
370 end | |
371 current_uid = pposix.getuid(); | |
372 local arg_root = arg[1] == "--root"; | |
373 if arg_root then table.remove(arg, 1); end | |
374 if current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then | |
375 -- We haz root! | |
376 local desired_user = config.get("*", "prosody_user") or "prosody"; | |
377 local desired_group = config.get("*", "prosody_group") or desired_user; | |
378 local ok, err = pposix.setgid(desired_group); | |
379 if ok then | |
380 ok, err = pposix.initgroups(desired_user); | |
381 end | |
382 if ok then | |
383 ok, err = pposix.setuid(desired_user); | |
384 if ok then | |
385 -- Yay! | |
386 switched_user = true; | |
387 end | |
388 end | |
389 if not switched_user then | |
390 -- Boo! | |
391 print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err)); | |
392 else | |
393 -- Make sure the Prosody user can read the config | |
394 local conf, err, errno = io.open(ENV_CONFIG); | |
395 if conf then | |
396 conf:close(); | |
397 else | |
398 print("The config file is not readable by the '"..desired_user.."' user."); | |
399 print("Prosody will not be able to read it."); | |
400 print("Error was "..err); | |
401 os.exit(1); | |
402 end | |
403 end | |
404 end | |
405 | |
406 -- Set our umask to protect data files | |
407 pposix.umask(config.get("*", "umask") or "027"); | |
408 pposix.setenv("HOME", data_path); | |
409 pposix.setenv("PROSODY_CONFIG", ENV_CONFIG); | |
410 else | |
411 print("Error: Unable to load pposix module. Check that Prosody is installed correctly.") | |
412 print("For more help send the below error to us through https://prosody.im/discuss"); | |
413 print(tostring(pposix)) | |
414 os.exit(1); | |
415 end | |
416 end | |
417 | |
418 function startup.check_unwriteable() | |
419 local function test_writeable(filename) | |
420 local f, err = io.open(filename, "a"); | |
421 if not f then | |
422 return false, err; | |
423 end | |
424 f:close(); | |
425 return true; | |
426 end | |
427 | |
428 local unwriteable_files = {}; | |
429 if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then | |
430 local ok, err = test_writeable(original_logging_config); | |
431 if not ok then | |
432 table.insert(unwriteable_files, err); | |
433 end | |
434 elseif type(original_logging_config) == "table" then | |
435 for _, rule in ipairs(original_logging_config) do | |
436 if rule.filename then | |
437 local ok, err = test_writeable(rule.filename); | |
438 if not ok then | |
439 table.insert(unwriteable_files, err); | |
440 end | |
441 end | |
442 end | |
443 end | |
444 | |
445 if #unwriteable_files > 0 then | |
446 print("One of more of the Prosody log files are not"); | |
447 print("writeable, please correct the errors and try"); | |
448 print("starting prosodyctl again."); | |
449 print(""); | |
450 for _, err in ipairs(unwriteable_files) do | |
451 print(err); | |
452 end | |
453 print(""); | |
454 os.exit(1); | |
455 end | |
456 end | |
457 | |
458 function startup.make_dummy_hosts() | |
459 -- When running under prosodyctl, we don't want to | |
460 -- fully initialize the server, so we populate prosody.hosts | |
461 -- with just enough things for most code to work correctly | |
462 prosody.core_post_stanza = function () end; -- TODO: mod_router! | |
463 local function make_host(hostname) | |
464 return { | |
465 type = "local", | |
466 events = prosody.events, | |
467 modules = {}, | |
468 sessions = {}, | |
469 users = require "core.usermanager".new_null_provider(hostname) | |
470 }; | |
471 end | |
472 | |
473 for hostname, config in pairs(config.getconfig()) do | |
474 hosts[hostname] = make_host(hostname); | |
475 end | |
476 end | |
477 | |
478 -- prosodyctl only | |
479 function startup.prosodyctl() | |
480 startup.read_config(); | |
481 startup.chdir(); | |
482 startup.check_dependencies(); | |
483 startup.force_console_logging(); | |
484 startup.init_global_state(); | |
485 startup.init_logging(); | |
486 startup.log_dependency_warnings(); | |
487 startup.check_unwriteable(); | |
488 startup.load_libraries(); | |
489 startup.init_global_protection(); | |
490 startup.init_http_client(); | |
491 startup.make_dummy_hosts(); | |
492 end | |
493 | |
494 function startup.prosody() | |
495 -- These actions are in a strict order, as many depend on | |
496 -- previous steps to have already been performed | |
497 startup.read_config(); | |
498 startup.sanity_check(); | |
499 startup.sandbox_require(); | |
500 startup.set_function_metatable(); | |
501 startup.check_dependencies(); | |
502 startup.load_libraries(); | |
503 startup.init_global_state(); | |
504 startup.init_logging(); | |
505 startup.chdir(); | |
506 startup.add_global_prosody_functions(); | |
507 startup.read_version(); | |
508 startup.log_greeting(); | |
509 startup.log_dependency_warnings(); | |
510 startup.load_secondary_libraries(); | |
511 startup.init_http_client(); | |
512 startup.init_data_store(); | |
513 startup.init_global_protection(); | |
514 startup.prepare_to_start(); | |
515 -- startup.notify_started(); | |
516 end | |
517 | |
518 return startup; |