Changeset

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
parents 8634:f6f62c92b642
children 8636:8691083420e4
files prosody prosodyctl util/startup.lua
diffstat 3 files changed, 532 insertions(+), 575 deletions(-) [+]
line wrap: on
line diff
--- a/prosody	Tue Mar 20 16:07:50 2018 +0000
+++ b/prosody	Tue Mar 20 16:10:37 2018 +0000
@@ -49,335 +49,11 @@
 	return 1;
 end
 
--- Global 'prosody' object
-local prosody = { events = require "util.events".new(); };
-_G.prosody = prosody;
-
--- Check dependencies
-local dependencies = require "util.dependencies";
-
--- Load the config-parsing module
-config = require "core.configmanager"
-
--- -- -- --
--- Define the functions we call during startup, the
--- actual startup happens right at the end, where these
--- functions get called
-
-function read_config()
-	local filenames = {};
-
-	local filename;
-	if arg[1] == "--config" and arg[2] then
-		table.insert(filenames, arg[2]);
-		if CFG_CONFIGDIR then
-			table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
-		end
-	elseif os.getenv("PROSODY_CONFIG") then -- Passed by prosodyctl
-			table.insert(filenames, os.getenv("PROSODY_CONFIG"));
-	else
-		table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
-	end
-	for _,_filename in ipairs(filenames) do
-		filename = _filename;
-		local file = io.open(filename);
-		if file then
-			file:close();
-			CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
-			break;
-		end
-	end
-	prosody.config_file = filename
-	local ok, level, err = config.load(filename);
-	if not ok then
-		print("\n");
-		print("**************************");
-		if level == "parser" then
-			print("A problem occured while reading the config file "..filename);
-			print("");
-			local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
-			if err:match("chunk has too many syntax levels$") then
-				print("An Include statement in a config file is including an already-included");
-				print("file and causing an infinite loop. An Include statement in a config file is...");
-			else
-				print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
-			end
-			print("");
-		elseif level == "file" then
-			print("Prosody was unable to find the configuration file.");
-			print("We looked for: "..filename);
-			print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
-			print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
-		end
-		print("More help on configuring Prosody can be found at https://prosody.im/doc/configure");
-		print("Good luck!");
-		print("**************************");
-		print("");
-		os.exit(1);
-	end
-end
-
-function check_dependencies()
-	if not dependencies.check_dependencies() then
-		os.exit(1);
-	end
-end
-
--- luacheck: globals socket server
-
-function load_libraries()
-	-- Load socket framework
-	-- luacheck: ignore 111/server 111/socket
-	socket = require "socket";
-	server = require "net.server"
-end
-
--- The global log() gets defined by loggingmanager
--- luacheck: ignore 113/log
-
-function init_logging()
-	-- Initialize logging
-	require "core.loggingmanager"
-end
-
-function log_dependency_warnings()
-	dependencies.log_warnings();
-end
-
-function sanity_check()
-	for host, host_config in pairs(config.getconfig()) do
-		if host ~= "*"
-		and host_config.enabled ~= false
-		and not host_config.component_module then
-			return;
-		end
-	end
-	log("error", "No enabled VirtualHost entries found in the config file.");
-	log("error", "At least one active host is required for Prosody to function. Exiting...");
-	os.exit(1);
-end
-
-function sandbox_require()
-	-- Replace require() with one that doesn't pollute _G, required
-	-- for neat sandboxing of modules
-	-- luacheck: ignore 113/getfenv 111/require
-	local _realG = _G;
-	local _real_require = require;
-	local getfenv = getfenv or function (f)
-		-- FIXME: This is a hack to replace getfenv() in Lua 5.2
-		local name, env = debug.getupvalue(debug.getinfo(f or 1).func, 1);
-		if name == "_ENV" then
-			return env;
-		end
-	end
-	function require(...)
-		local curr_env = getfenv(2);
-		local curr_env_mt = getmetatable(curr_env);
-		local _realG_mt = getmetatable(_realG);
-		if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
-			local old_newindex, old_index;
-			old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
-			old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k) -- luacheck: ignore 212/_G
-				return rawget(curr_env, k);
-			end;
-			local ret = _real_require(...);
-			_realG_mt.__newindex = old_newindex;
-			_realG_mt.__index = old_index;
-			return ret;
-		end
-		return _real_require(...);
-	end
-end
+local startup = require "util.startup";
 
-function set_function_metatable()
-	local mt = {};
-	function mt.__index(f, upvalue)
-		local i, name, value = 0;
-		repeat
-			i = i + 1;
-			name, value = debug.getupvalue(f, i);
-		until name == upvalue or name == nil;
-		return value;
-	end
-	function mt.__newindex(f, upvalue, value)
-		local i, name = 0;
-		repeat
-			i = i + 1;
-			name = debug.getupvalue(f, i);
-		until name == upvalue or name == nil;
-		if name then
-			debug.setupvalue(f, i, value);
-		end
-	end
-	function mt.__tostring(f)
-		local info = debug.getinfo(f);
-		return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
-	end
-	debug.setmetatable(function() end, mt);
-end
-
-function init_global_state()
-	prosody.bare_sessions = {};
-	prosody.full_sessions = {};
-	prosody.hosts = {};
-
-	-- COMPAT: These globals are deprecated
-	-- luacheck: ignore 111/bare_sessions 111/full_sessions 111/hosts
-	bare_sessions = prosody.bare_sessions;
-	full_sessions = prosody.full_sessions;
-	hosts = prosody.hosts;
-
-	local data_path = config.get("*", "data_path") or CFG_DATADIR or "data";
-	local custom_plugin_paths = config.get("*", "plugin_paths");
-	if custom_plugin_paths then
-		local path_sep = package.config:sub(3,3);
-		-- path1;path2;path3;defaultpath...
-		CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
-	end
-	prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".",
-	                  plugins = CFG_PLUGINDIR or "plugins", data = data_path };
-
-	prosody.arg = _G.arg;
-
-	prosody.platform = "unknown";
-	if os.getenv("WINDIR") then
-		prosody.platform = "windows";
-	elseif package.config:sub(1,1) == "/" then
-		prosody.platform = "posix";
-	end
-
-	prosody.installed = nil;
-	if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
-		prosody.installed = true;
-	end
-
-	if prosody.installed then
-		-- Change working directory to data path.
-		require "lfs".chdir(data_path);
-	end
-
-	-- Function to reload the config file
-	function prosody.reload_config()
-		log("info", "Reloading configuration file");
-		prosody.events.fire_event("reloading-config");
-		local ok, level, err = config.load(prosody.config_file);
-		if not ok then
-			if level == "parser" then
-				log("error", "There was an error parsing the configuration file: %s", tostring(err));
-			elseif level == "file" then
-				log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
-			end
-		end
-		return ok, (err and tostring(level)..": "..tostring(err)) or nil;
-	end
-
-	-- Function to reopen logfiles
-	function prosody.reopen_logfiles()
-		log("info", "Re-opening log files");
-		prosody.events.fire_event("reopen-log-files");
-	end
+startup.prosody();
 
-	-- Function to initiate prosody shutdown
-	function prosody.shutdown(reason, code)
-		log("info", "Shutting down: %s", reason or "unknown reason");
-		prosody.shutdown_reason = reason;
-		prosody.shutdown_code = code;
-		prosody.events.fire_event("server-stopping", {
-			reason = reason;
-			code = code;
-		});
-		server.setquitting(true);
-	end
-end
-
-function read_version()
-	-- Try to determine version
-	local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
-	if version_file then
-		prosody.version = version_file:read("*a"):gsub("%s*$", "");
-		version_file:close();
-		if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
-			prosody.version = "hg:"..prosody.version;
-		end
-	else
-		prosody.version = "unknown";
-	end
-end
-
-function load_secondary_libraries()
-	--- Load and initialise core modules
-	require "util.import"
-	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()
-		log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)"));
-		return function() end
-	end});
-
-	local http = require "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",
-		{ capath = config_ssl.capath, cafile = config_ssl.cafile, verify = "peer", }, https_client);
-
-	require "util.array"
-	require "util.datetime"
-	require "util.iterators"
-	require "util.timer"
-	require "util.helpers"
-
-	pcall(require, "util.signal") -- Not on Windows
-
-	-- Commented to protect us from
-	-- the second kind of people
-	--[[
-	pcall(require, "remdebug.engine");
-	if remdebug then remdebug.engine.start() end
-	]]
-
-	require "util.stanza"
-	require "util.jid"
-end
-
-function init_data_store()
-	require "core.storagemanager";
-end
-
-function 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();
-end
-
-function init_global_protection()
-	-- Catch global accesses
-	-- luacheck: ignore 212/t
-	local locked_globals_mt = {
-		__index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
-		__newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
-	};
-
-	function prosody.unlock_globals()
-		setmetatable(_G, nil);
-	end
-
-	function prosody.lock_globals()
-		setmetatable(_G, locked_globals_mt);
-	end
-
-	-- And lock now...
-	prosody.lock_globals();
-end
-
-function loop()
+local function loop()
 	-- Error handler for errors that make it this far
 	local function catch_uncaught_error(err)
 		if type(err) == "string" and err:match("interrupted!$") then
@@ -400,32 +76,11 @@
 	end
 end
 
-function cleanup()
+local function cleanup()
 	log("info", "Shutdown status: Cleaning up");
 	prosody.events.fire_event("server-cleanup");
 end
 
--- Are you ready? :)
--- These actions are in a strict order, as many depend on
--- previous steps to have already been performed
-read_config();
-init_logging();
-sanity_check();
-sandbox_require();
-set_function_metatable();
-check_dependencies();
-load_libraries();
-init_global_state();
-read_version();
-log("info", "Hello and welcome to Prosody version %s", prosody.version);
-log_dependency_warnings();
-load_secondary_libraries();
-init_data_store();
-init_global_protection();
-prepare_to_start();
-
-prosody.events.fire_event("server-started");
-
 loop();
 
 log("info", "Shutting down...");
--- a/prosodyctl	Tue Mar 20 16:07:50 2018 +0000
+++ b/prosodyctl	Tue Mar 20 16:10:37 2018 +0000
@@ -43,188 +43,11 @@
 	end
 end
 
--- Global 'prosody' object
-local prosody = {
-	hosts = {};
-	events = require "util.events".new();
-	platform = "posix";
-	lock_globals = function () end;
-	unlock_globals = function () end;
-	installed = CFG_SOURCEDIR ~= nil;
-	core_post_stanza = function () end; -- TODO: mod_router!
-};
-_G.prosody = prosody;
-
-local dependencies = require "util.dependencies";
-if not dependencies.check_dependencies() then
-	os.exit(1);
-end
-
-config = require "core.configmanager"
-
-local ENV_CONFIG;
-do
-	local filenames = {};
-
-	local filename;
-	if arg[1] == "--config" and arg[2] then
-		table.insert(filenames, arg[2]);
-		if CFG_CONFIGDIR then
-			table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
-		end
-		table.remove(arg, 1); table.remove(arg, 1);
-	else
-		table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
-	end
-	for _,_filename in ipairs(filenames) do
-		filename = _filename;
-		local file = io.open(filename);
-		if file then
-			file:close();
-			ENV_CONFIG = filename;
-			CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
-			break;
-		end
-	end
-	local ok, level, err = config.load(filename);
-	if not ok then
-		print("\n");
-		print("**************************");
-		if level == "parser" then
-			print("A problem occured while reading the config file "..filename);
-			local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
-			print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
-			print("");
-		elseif level == "file" then
-			print("Prosody was unable to find the configuration file.");
-			print("We looked for: "..filename);
-			print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
-			print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
-		end
-		print("More help on configuring Prosody can be found at https://prosody.im/doc/configure");
-		print("Good luck!");
-		print("**************************");
-		print("");
-		os.exit(1);
-	end
-end
-local original_logging_config = config.get("*", "log");
-config.set("*", "log", { { levels = { min="info" }, to = "console" } });
-
-local data_path = config.get("*", "data_path") or CFG_DATADIR or "data";
-local custom_plugin_paths = config.get("*", "plugin_paths");
-if custom_plugin_paths then
-	local path_sep = package.config:sub(3,3);
-	-- path1;path2;path3;defaultpath...
-	CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
-end
-prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR,
-	          plugins = CFG_PLUGINDIR or "plugins", data = data_path };
-
-if prosody.installed then
-	-- Change working directory to data path.
-	require "lfs".chdir(data_path);
-end
-
-require "core.loggingmanager"
-
-dependencies.log_warnings();
-
--- Switch away from root and into the prosody user --
-local switched_user, current_uid;
+-----------
 
-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)); return;
-	end
-	current_uid = pposix.getuid();
-	local arg_root = arg[1] == "--root";
-	if arg_root then table.remove(arg, 1); end
-	if current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then
-		-- We haz root!
-		local desired_user = config.get("*", "prosody_user") or "prosody";
-		local desired_group = config.get("*", "prosody_group") or desired_user;
-		local ok, err = pposix.setgid(desired_group);
-		if ok then
-			ok, err = pposix.initgroups(desired_user);
-		end
-		if ok then
-			ok, err = pposix.setuid(desired_user);
-			if ok then
-				-- Yay!
-				switched_user = true;
-			end
-		end
-		if not switched_user then
-			-- Boo!
-			print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err));
-		else
-			-- Make sure the Prosody user can read the config
-			local conf, err, errno = io.open(ENV_CONFIG);
-			if conf then
-				conf:close();
-			else
-				print("The config file is not readable by the '"..desired_user.."' user.");
-				print("Prosody will not be able to read it.");
-				print("Error was "..err);
-				os.exit(1);
-			end
-		end
-	end
+require "util.startup".prosodyctl();
 
-	-- Set our umask to protect data files
-	pposix.umask(config.get("*", "umask") or "027");
-	pposix.setenv("HOME", data_path);
-	pposix.setenv("PROSODY_CONFIG", ENV_CONFIG);
-else
-	print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
-	print("For more help send the below error to us through https://prosody.im/discuss");
-	print(tostring(pposix))
-	os.exit(1);
-end
-
-local function test_writeable(filename)
-	local f, err = io.open(filename, "a");
-	if not f then
-		return false, err;
-	end
-	f:close();
-	return true;
-end
-
-local unwriteable_files = {};
-if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then
-	local ok, err = test_writeable(original_logging_config);
-	if not ok then
-		table.insert(unwriteable_files, err);
-	end
-elseif type(original_logging_config) == "table" then
-	for _, rule in ipairs(original_logging_config) do
-		if rule.filename then
-			local ok, err = test_writeable(rule.filename);
-			if not ok then
-				table.insert(unwriteable_files, err);
-			end
-		end
-	end
-end
-
-if #unwriteable_files > 0 then
-	print("One of more of the Prosody log files are not");
-	print("writeable, please correct the errors and try");
-	print("starting prosodyctl again.");
-	print("");
-	for _, err in ipairs(unwriteable_files) do
-		print(err);
-	end
-	print("");
-	os.exit(1);
-end
-
+-----------
 
 local error_messages = setmetatable({
 		["invalid-username"] = "The given username is invalid in a Jabber ID";
@@ -240,53 +63,14 @@
 		["not-running"] = "Prosody is not running";
 		}, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
 
-hosts = prosody.hosts;
-
-local function make_host(hostname)
-	return {
-		type = "local",
-		events = prosody.events,
-		modules = {},
-		sessions = {},
-		users = require "core.usermanager".new_null_provider(hostname)
-	};
-end
-
-for hostname, config in pairs(config.getconfig()) do
-	hosts[hostname] = make_host(hostname);
-end
-
+local config = require "core.configmanager";
 local modulemanager = require "core.modulemanager"
-
 local prosodyctl = require "util.prosodyctl"
 local socket = require "socket"
-
-local http = require "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",
-	{ capath = config_ssl.capath, cafile = config_ssl.cafile, verify = "peer", }, https_client);
+local dependencies = require "util.dependencies";
 
 -----------------------
 
- -- FIXME: Duplicate code waiting for util.startup
-function read_version()
-	-- Try to determine version
-	local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
-	prosody.version = "unknown";
-	if version_file then
-		prosody.version = version_file:read("*a"):gsub("%s*$", "");
-		version_file:close();
-		if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
-			prosody.version = "hg:"..prosody.version;
-		end
-	else
-		local hg = require"util.mercurial";
-		local hgid = hg.check_id(CFG_SOURCEDIR or ".");
-		if hgid then prosody.version = "hg:" .. hgid; end
-	end
-end
-
 local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning;
 local show_usage = prosodyctl.show_usage;
 local show_yesno = prosodyctl.show_yesno;
@@ -546,7 +330,6 @@
 end
 
 function commands.about(arg)
-	read_version();
 	if arg[1] == "--help" then
 		show_usage([[about]], [[Show information about this Prosody installation]]);
 		return 1;
@@ -562,9 +345,9 @@
 	print("Prosody "..(prosody.version or "(unknown version)"));
 	print("");
 	print("# Prosody directories");
-	print("Data directory:     "..relpath(pwd, data_path));
-	print("Config directory:   "..relpath(pwd, CFG_CONFIGDIR or "."));
-	print("Source directory:   "..relpath(pwd, CFG_SOURCEDIR or "."));
+	print("Data directory:     "..relpath(pwd, prosody.paths.data));
+	print("Config directory:   "..relpath(pwd, prosody.paths.config or "."));
+	print("Source directory:   "..relpath(pwd, prosody.paths.source or "."));
 	print("Plugin directories:")
 	print("  "..(prosody.paths.plugins:gsub("([^;]+);?", function(path)
 			path = config.resolve_relative_path(pwd, path);
@@ -716,7 +499,8 @@
 	end
 end
 
-local cert_basedir = CFG_DATADIR or "./certs";
+local have_pposix, pposix = pcall(require, "util.pposix");
+local cert_basedir = prosody.paths.data == "." and "./certs" or prosody.paths.data;
 if have_pposix and pposix.getuid() == 0 then
 	-- FIXME should be enough to check if this directory is writable
 	local cert_dir = config.get("*", "certificates") or "certs";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/startup.lua	Tue Mar 20 16:10:37 2018 +0000
@@ -0,0 +1,518 @@
+local startup = {};
+
+local prosody = { events = require "util.events".new() };
+
+local config = require "core.configmanager";
+
+local dependencies = require "util.dependencies";
+
+function startup.read_config()
+	local filenames = {};
+
+	local filename;
+	if arg[1] == "--config" and arg[2] then
+		table.insert(filenames, arg[2]);
+		if CFG_CONFIGDIR then
+			table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
+		end
+		table.remove(arg, 1); table.remove(arg, 1);
+	elseif os.getenv("PROSODY_CONFIG") then -- Passed by prosodyctl
+			table.insert(filenames, os.getenv("PROSODY_CONFIG"));
+	else
+		table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
+	end
+	for _,_filename in ipairs(filenames) do
+		filename = _filename;
+		local file = io.open(filename);
+		if file then
+			file:close();
+			prosody.config_file = filename;
+			CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
+			break;
+		end
+	end
+	prosody.config_file = filename
+	local ok, level, err = config.load(filename);
+	if not ok then
+		print("\n");
+		print("**************************");
+		if level == "parser" then
+			print("A problem occured while reading the config file "..filename);
+			print("");
+			local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
+			if err:match("chunk has too many syntax levels$") then
+				print("An Include statement in a config file is including an already-included");
+				print("file and causing an infinite loop. An Include statement in a config file is...");
+			else
+				print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
+			end
+			print("");
+		elseif level == "file" then
+			print("Prosody was unable to find the configuration file.");
+			print("We looked for: "..filename);
+			print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
+			print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
+		end
+		print("More help on configuring Prosody can be found at https://prosody.im/doc/configure");
+		print("Good luck!");
+		print("**************************");
+		print("");
+		os.exit(1);
+	end
+end
+
+function startup.check_dependencies()
+	if not dependencies.check_dependencies() then
+		os.exit(1);
+	end
+end
+
+-- luacheck: globals socket server
+
+function startup.load_libraries()
+	-- Load socket framework
+	-- luacheck: ignore 111/server 111/socket
+	socket = require "socket";
+	server = require "net.server"
+end
+
+-- The global log() gets defined by loggingmanager
+-- luacheck: ignore 113/log
+
+function startup.init_logging()
+	-- Initialize logging
+	require "core.loggingmanager"
+end
+
+function startup.log_dependency_warnings()
+	dependencies.log_warnings();
+end
+
+function startup.sanity_check()
+	for host, host_config in pairs(config.getconfig()) do
+		if host ~= "*"
+		and host_config.enabled ~= false
+		and not host_config.component_module then
+			return;
+		end
+	end
+	log("error", "No enabled VirtualHost entries found in the config file.");
+	log("error", "At least one active host is required for Prosody to function. Exiting...");
+	os.exit(1);
+end
+
+function startup.sandbox_require()
+	-- Replace require() with one that doesn't pollute _G, required
+	-- for neat sandboxing of modules
+	-- luacheck: ignore 113/getfenv 111/require
+	local _realG = _G;
+	local _real_require = require;
+	local getfenv = getfenv or function (f)
+		-- FIXME: This is a hack to replace getfenv() in Lua 5.2
+		local name, env = debug.getupvalue(debug.getinfo(f or 1).func, 1);
+		if name == "_ENV" then
+			return env;
+		end
+	end
+	function require(...)
+		local curr_env = getfenv(2);
+		local curr_env_mt = getmetatable(curr_env);
+		local _realG_mt = getmetatable(_realG);
+		if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
+			local old_newindex, old_index;
+			old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
+			old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k) -- luacheck: ignore 212/_G
+				return rawget(curr_env, k);
+			end;
+			local ret = _real_require(...);
+			_realG_mt.__newindex = old_newindex;
+			_realG_mt.__index = old_index;
+			return ret;
+		end
+		return _real_require(...);
+	end
+end
+
+function startup.set_function_metatable()
+	local mt = {};
+	function mt.__index(f, upvalue)
+		local i, name, value = 0;
+		repeat
+			i = i + 1;
+			name, value = debug.getupvalue(f, i);
+		until name == upvalue or name == nil;
+		return value;
+	end
+	function mt.__newindex(f, upvalue, value)
+		local i, name = 0;
+		repeat
+			i = i + 1;
+			name = debug.getupvalue(f, i);
+		until name == upvalue or name == nil;
+		if name then
+			debug.setupvalue(f, i, value);
+		end
+	end
+	function mt.__tostring(f)
+		local info = debug.getinfo(f);
+		return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
+	end
+	debug.setmetatable(function() end, mt);
+end
+
+function startup.detect_platform()
+	prosody.platform = "unknown";
+	if os.getenv("WINDIR") then
+		prosody.platform = "windows";
+	elseif package.config:sub(1,1) == "/" then
+		prosody.platform = "posix";
+	end
+end
+
+function startup.detect_installed()
+	prosody.installed = nil;
+	if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
+		prosody.installed = true;
+	end
+end
+
+function startup.chdir()
+	if prosody.installed then
+		-- Change working directory to data path.
+		require "lfs".chdir(data_path);
+	end
+end
+
+function startup.init_global_state()
+	prosody.bare_sessions = {};
+	prosody.full_sessions = {};
+	prosody.hosts = {};
+
+	-- COMPAT: These globals are deprecated
+	-- luacheck: ignore 111/bare_sessions 111/full_sessions 111/hosts
+	bare_sessions = prosody.bare_sessions;
+	full_sessions = prosody.full_sessions;
+	hosts = prosody.hosts;
+
+	local data_path = config.get("*", "data_path") or CFG_DATADIR or "data";
+	local custom_plugin_paths = config.get("*", "plugin_paths");
+	if custom_plugin_paths then
+		local path_sep = package.config:sub(3,3);
+		-- path1;path2;path3;defaultpath...
+		CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
+	end
+	prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".",
+	                  plugins = CFG_PLUGINDIR or "plugins", data = data_path };
+
+	prosody.arg = _G.arg;
+
+	startup.detect_platform();
+	startup.detect_installed();
+	_G.prosody = prosody;
+end
+
+function startup.add_global_prosody_functions()
+	-- Function to reload the config file
+	function prosody.reload_config()
+		log("info", "Reloading configuration file");
+		prosody.events.fire_event("reloading-config");
+		local ok, level, err = config.load(prosody.config_file);
+		if not ok then
+			if level == "parser" then
+				log("error", "There was an error parsing the configuration file: %s", tostring(err));
+			elseif level == "file" then
+				log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
+			end
+		end
+		return ok, (err and tostring(level)..": "..tostring(err)) or nil;
+	end
+
+	-- Function to reopen logfiles
+	function prosody.reopen_logfiles()
+		log("info", "Re-opening log files");
+		prosody.events.fire_event("reopen-log-files");
+	end
+
+	-- Function to initiate prosody shutdown
+	function prosody.shutdown(reason, code)
+		log("info", "Shutting down: %s", reason or "unknown reason");
+		prosody.shutdown_reason = reason;
+		prosody.shutdown_code = code;
+		prosody.events.fire_event("server-stopping", {
+			reason = reason;
+			code = code;
+		});
+		server.setquitting(true);
+	end
+end
+
+function startup.load_secondary_libraries()
+	--- Load and initialise core modules
+	require "util.import"
+	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()
+		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
+
+	-- Commented to protect us from
+	-- the second kind of people
+	--[[
+	pcall(require, "remdebug.engine");
+	if remdebug then remdebug.engine.start() end
+	]]
+
+	require "util.stanza"
+	require "util.jid"
+end
+
+function startup.init_http_client()
+	local http = require "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",
+		{ capath = config_ssl.capath, cafile = config_ssl.cafile, verify = "peer", }, https_client);
+end
+
+function startup.init_data_store()
+	require "core.storagemanager";
+end
+
+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();
+end
+
+function startup.init_global_protection()
+	-- Catch global accesses
+	-- luacheck: ignore 212/t
+	local locked_globals_mt = {
+		__index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
+		__newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
+	};
+
+	function prosody.unlock_globals()
+		setmetatable(_G, nil);
+	end
+
+	function prosody.lock_globals()
+		setmetatable(_G, locked_globals_mt);
+	end
+
+	-- And lock now...
+	prosody.lock_globals();
+end
+
+function startup.read_version()
+	-- Try to determine version
+	local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
+	prosody.version = "unknown";
+	if version_file then
+		prosody.version = version_file:read("*a"):gsub("%s*$", "");
+		version_file:close();
+		if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
+			prosody.version = "hg:"..prosody.version;
+		end
+	else
+		local hg = require"util.mercurial";
+		local hgid = hg.check_id(CFG_SOURCEDIR or ".");
+		if hgid then prosody.version = "hg:" .. hgid; end
+	end
+end
+
+function startup.log_greeting()
+	log("info", "Hello and welcome to Prosody version %s", prosody.version);
+end
+
+function startup.notify_started()
+	prosody.events.fire_event("server-started");	
+end
+
+-- Override logging config (used by prosodyctl)
+function startup.force_console_logging()
+	local original_logging_config = config.get("*", "log");
+	config.set("*", "log", { { levels = { min="info" }, to = "console" } });
+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 switched_user, current_uid;
+
+	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
+		current_uid = pposix.getuid();
+		local arg_root = arg[1] == "--root";
+		if arg_root then table.remove(arg, 1); end
+		if current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then
+			-- We haz root!
+			local desired_user = config.get("*", "prosody_user") or "prosody";
+			local desired_group = config.get("*", "prosody_group") or desired_user;
+			local ok, err = pposix.setgid(desired_group);
+			if ok then
+				ok, err = pposix.initgroups(desired_user);
+			end
+			if ok then
+				ok, err = pposix.setuid(desired_user);
+				if ok then
+					-- Yay!
+					switched_user = true;
+				end
+			end
+			if not switched_user then
+				-- Boo!
+				print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err));
+			else
+				-- Make sure the Prosody user can read the config
+				local conf, err, errno = io.open(ENV_CONFIG);
+				if conf then
+					conf:close();
+				else
+					print("The config file is not readable by the '"..desired_user.."' user.");
+					print("Prosody will not be able to read it.");
+					print("Error was "..err);
+					os.exit(1);
+				end
+			end
+		end
+	
+		-- Set our umask to protect data files
+		pposix.umask(config.get("*", "umask") or "027");
+		pposix.setenv("HOME", data_path);
+		pposix.setenv("PROSODY_CONFIG", ENV_CONFIG);
+	else
+		print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
+		print("For more help send the below error to us through https://prosody.im/discuss");
+		print(tostring(pposix))
+		os.exit(1);
+	end
+end
+
+function startup.check_unwriteable()
+	local function test_writeable(filename)
+		local f, err = io.open(filename, "a");
+		if not f then
+			return false, err;
+		end
+		f:close();
+		return true;
+	end
+	
+	local unwriteable_files = {};
+	if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then
+		local ok, err = test_writeable(original_logging_config);
+		if not ok then
+			table.insert(unwriteable_files, err);
+		end
+	elseif type(original_logging_config) == "table" then
+		for _, rule in ipairs(original_logging_config) do
+			if rule.filename then
+				local ok, err = test_writeable(rule.filename);
+				if not ok then
+					table.insert(unwriteable_files, err);
+				end
+			end
+		end
+	end
+	
+	if #unwriteable_files > 0 then
+		print("One of more of the Prosody log files are not");
+		print("writeable, please correct the errors and try");
+		print("starting prosodyctl again.");
+		print("");
+		for _, err in ipairs(unwriteable_files) do
+			print(err);
+		end
+		print("");
+		os.exit(1);
+	end
+end
+
+function startup.make_dummy_hosts()
+	-- When running under prosodyctl, we don't want to
+	-- fully initialize the server, so we populate prosody.hosts
+	-- with just enough things for most code to work correctly
+	prosody.core_post_stanza = function () end; -- TODO: mod_router!
+	local function make_host(hostname)
+		return {
+			type = "local",
+			events = prosody.events,
+			modules = {},
+			sessions = {},
+			users = require "core.usermanager".new_null_provider(hostname)
+		};
+	end
+	
+	for hostname, config in pairs(config.getconfig()) do
+		hosts[hostname] = make_host(hostname);
+	end
+end
+
+-- prosodyctl only
+function startup.prosodyctl()
+	startup.read_config();
+	startup.chdir();
+	startup.check_dependencies();
+	startup.force_console_logging();
+	startup.init_global_state();
+	startup.init_logging();
+	startup.log_dependency_warnings();
+	startup.check_unwriteable();
+	startup.load_libraries();
+	startup.init_global_protection();
+	startup.init_http_client();
+	startup.make_dummy_hosts();
+end
+
+function startup.prosody()
+	-- These actions are in a strict order, as many depend on
+	-- previous steps to have already been performed
+	startup.read_config();
+	startup.sanity_check();
+	startup.sandbox_require();
+	startup.set_function_metatable();
+	startup.check_dependencies();
+	startup.load_libraries();
+	startup.init_global_state();
+	startup.init_logging();
+	startup.chdir();
+	startup.add_global_prosody_functions();
+	startup.read_version();
+	startup.log_greeting();
+	startup.log_dependency_warnings();
+	startup.load_secondary_libraries();
+	startup.init_http_client();
+	startup.init_data_store();
+	startup.init_global_protection();
+	startup.prepare_to_start();
+--	startup.notify_started();
+end
+
+return startup;