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; |