Software / code / prosody
Comparison
plugins/mod_admin_shell.lua @ 13349:d5d4e386c6fb
mod_admin_shell: Refactor help to data structures for extensibility
This makes it easier for commands added by other modules to add to the help
output, for example.
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Wed, 29 Nov 2023 17:18:17 +0000 |
| parent | 13338:470ab6b55e93 |
| child | 13350:0573401b0f9f |
comparison
equal
deleted
inserted
replaced
| 13348:47ede3d51833 | 13349:d5d4e386c6fb |
|---|---|
| 13 local hostmanager = require "prosody.core.hostmanager"; | 13 local hostmanager = require "prosody.core.hostmanager"; |
| 14 local modulemanager = require "prosody.core.modulemanager"; | 14 local modulemanager = require "prosody.core.modulemanager"; |
| 15 local s2smanager = require "prosody.core.s2smanager"; | 15 local s2smanager = require "prosody.core.s2smanager"; |
| 16 local portmanager = require "prosody.core.portmanager"; | 16 local portmanager = require "prosody.core.portmanager"; |
| 17 local helpers = require "prosody.util.helpers"; | 17 local helpers = require "prosody.util.helpers"; |
| 18 local it = require "prosody.util.iterators"; | |
| 18 local server = require "prosody.net.server"; | 19 local server = require "prosody.net.server"; |
| 19 local st = require "prosody.util.stanza"; | 20 local st = require "prosody.util.stanza"; |
| 20 | 21 |
| 21 local _G = _G; | 22 local _G = _G; |
| 22 | 23 |
| 61 | 62 |
| 62 local commands = module:shared("commands") | 63 local commands = module:shared("commands") |
| 63 local def_env = module:shared("env"); | 64 local def_env = module:shared("env"); |
| 64 local default_env_mt = { __index = def_env }; | 65 local default_env_mt = { __index = def_env }; |
| 65 | 66 |
| 67 local function new_section(section_desc) | |
| 68 return setmetatable({}, { | |
| 69 help = { | |
| 70 desc = section_desc; | |
| 71 commands = {}; | |
| 72 }; | |
| 73 }); | |
| 74 end | |
| 75 | |
| 76 local help_topics = {}; | |
| 77 local function help_topic(name) | |
| 78 return function (desc) | |
| 79 return function (content) | |
| 80 help_topics[name] = { | |
| 81 desc = desc; | |
| 82 content = content; | |
| 83 }; | |
| 84 end; | |
| 85 end | |
| 86 end | |
| 87 | |
| 88 -- Seed with default sections and their description text | |
| 89 help_topic "console" "Help regarding the console itself" [[ | |
| 90 Hey! Welcome to Prosody's admin console. | |
| 91 First thing, if you're ever wondering how to get out, simply type 'quit'. | |
| 92 Secondly, note that we don't support the full telnet protocol yet (it's coming) | |
| 93 so you may have trouble using the arrow keys, etc. depending on your system. | |
| 94 | |
| 95 For now we offer a couple of handy shortcuts: | |
| 96 !! - Repeat the last command | |
| 97 !old!new! - repeat the last command, but with 'old' replaced by 'new' | |
| 98 | |
| 99 For those well-versed in Prosody's internals, or taking instruction from those who are, | |
| 100 you can prefix a command with > to escape the console sandbox, and access everything in | |
| 101 the running server. Great fun, but be careful not to break anything :) | |
| 102 ]]; | |
| 103 | |
| 104 local available_columns; --forward declaration so it is reachable from the help | |
| 105 | |
| 106 help_topic "columns" "Information about customizing session listings" (function (self, print) | |
| 107 print [[The columns shown by c2s:show() and s2s:show() can be customizied via the]] | |
| 108 print [['columns' argument as described here.]] | |
| 109 print [[]] | |
| 110 print [[Columns can be specified either as "id jid ipv" or as {"id", "jid", "ipv"}.]] | |
| 111 print [[Available columns are:]] | |
| 112 local meta_columns = { | |
| 113 { title = "ID"; width = 5 }; | |
| 114 { title = "Column Title"; width = 12 }; | |
| 115 { title = "Description"; width = 12 }; | |
| 116 }; | |
| 117 -- auto-adjust widths | |
| 118 for column, spec in pairs(available_columns) do | |
| 119 meta_columns[1].width = math.max(meta_columns[1].width or 0, #column); | |
| 120 meta_columns[2].width = math.max(meta_columns[2].width or 0, #(spec.title or "")); | |
| 121 meta_columns[3].width = math.max(meta_columns[3].width or 0, #(spec.description or "")); | |
| 122 end | |
| 123 local row = format_table(meta_columns, self.session.width) | |
| 124 print(row()); | |
| 125 for column, spec in iterators.sorted_pairs(available_columns) do | |
| 126 print(row({ column, spec.title, spec.description })); | |
| 127 end | |
| 128 print [[]] | |
| 129 print [[Most fields on the internal session structures can also be used as columns]] | |
| 130 -- Also, you can pass a table column specification directly, with mapper callback and all | |
| 131 end); | |
| 132 | |
| 133 help_topic "roles" "Show information about user roles" [[ | |
| 134 Roles may grant access or restrict users from certain operations. | |
| 135 | |
| 136 Built-in roles are: | |
| 137 prosody:guest - Guest/anonymous user | |
| 138 prosody:registered - Registered user | |
| 139 prosody:member - Provisioned user | |
| 140 prosody:admin - Host administrator | |
| 141 prosody:operator - Server administrator | |
| 142 | |
| 143 Roles can be assigned using the user management commands (see 'help user'). | |
| 144 ]]; | |
| 145 | |
| 146 | |
| 66 local function redirect_output(target, session) | 147 local function redirect_output(target, session) |
| 67 local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end }); | 148 local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end }); |
| 68 env.dofile = function(name) | 149 env.dofile = function(name) |
| 69 local f, err = envloadfile(name, env); | 150 local f, err = envloadfile(name, env); |
| 70 if not f then return f, err; end | 151 if not f then return f, err; end |
| 141 local line = event.stanza:get_text(); | 222 local line = event.stanza:get_text(); |
| 142 local useglobalenv; | 223 local useglobalenv; |
| 143 | 224 |
| 144 local result = st.stanza("repl-result"); | 225 local result = st.stanza("repl-result"); |
| 145 | 226 |
| 227 module:log("warn", "Processing line: %q", line); | |
| 228 | |
| 146 if line:match("^>") then | 229 if line:match("^>") then |
| 147 line = line:gsub("^>", ""); | 230 line = line:gsub("^>", ""); |
| 148 useglobalenv = true; | 231 useglobalenv = true; |
| 149 else | 232 else |
| 150 local command = line:match("^%w+") or line:match("%p"); | 233 local command = line:match("^(%w+) ") or line:match("^%w+$") or line:match("%p"); |
| 151 if commands[command] then | 234 if commands[command] then |
| 152 commands[command](session, line); | 235 commands[command](session, line); |
| 153 event.origin.send(result); | 236 event.origin.send(result); |
| 154 return; | 237 return; |
| 155 end | 238 end |
| 211 event.origin.send(st.stanza("repl-result", { type = "error" }):text(err)); | 294 event.origin.send(st.stanza("repl-result", { type = "error" }):text(err)); |
| 212 end | 295 end |
| 213 return true; | 296 return true; |
| 214 end); | 297 end); |
| 215 | 298 |
| 299 local function describe_command(s) | |
| 300 local section, name, args, desc = s:match("^([%w_]+):([%w_]+)%(([^)]*)%) %- (.+)$"); | |
| 301 if not section then | |
| 302 error("Failed to parse command description: "..s); | |
| 303 end | |
| 304 local command_help = getmetatable(def_env[section]).help.commands; | |
| 305 command_help[name] = { | |
| 306 desc = desc; | |
| 307 args = array.collect(args:gmatch("[%w_]+")):map(function (name) | |
| 308 return { name = name }; | |
| 309 end); | |
| 310 }; | |
| 311 end | |
| 312 | |
| 216 -- Console commands -- | 313 -- Console commands -- |
| 217 -- These are simple commands, not valid standalone in Lua | 314 -- These are simple commands, not valid standalone in Lua |
| 218 | 315 |
| 219 local available_columns; --forward declaration so it is reachable from the help | 316 -- Help about individual topics is handled by def_env.help |
| 220 | |
| 221 function commands.help(session, data) | 317 function commands.help(session, data) |
| 222 local print = session.print; | 318 local print = session.print; |
| 223 local section = data:match("^help (%w+)"); | 319 local section = data:match("^help (%w+)"); |
| 224 if not section then | 320 if not section then |
| 225 print [[Commands are divided into multiple sections. For help on a particular section, ]] | 321 print [[Commands are divided into multiple sections. For help on a particular section, ]] |
| 226 print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] | 322 print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] |
| 227 print [[]] | 323 print [[]] |
| 228 local row = format_table({ { title = "Section", width = 7 }, { title = "Description", width = "100%" } }, session.width) | 324 local row = format_table({ { title = "Section", width = 7 }, { title = "Description", width = "100%" } }, session.width) |
| 229 print(row()) | 325 print(row()) |
| 230 print(row { "c2s"; "Commands to manage local client-to-server sessions" }) | 326 for section_name, section in it.sorted_pairs(def_env) do |
| 231 print(row { "s2s"; "Commands to manage sessions between this server and others" }) | 327 local section_mt = getmetatable(section); |
| 232 print(row { "http"; "Commands to inspect HTTP services" }) -- XXX plural but there is only one so far | 328 local section_help = section_mt and section_mt.help; |
| 233 print(row { "module"; "Commands to load/reload/unload modules/plugins" }) | 329 print(row { section_name; section_help and section_help.desc or "" }); |
| 234 print(row { "host"; "Commands to activate, deactivate and list virtual hosts" }) | 330 end |
| 235 print(row { "user"; "Commands to create and delete users, and change their passwords" }) | 331 else |
| 236 print(row { "roles"; "Show information about user roles" }) | 332 return def_env.help[section]({ session = session }); |
| 237 print(row { "muc"; "Commands to create, list and manage chat rooms" }) | 333 end |
| 238 print(row { "stats"; "Commands to show internal statistics" }) | 334 |
| 239 print(row { "server"; "Uptime, version, shutting down, etc." }) | 335 print(""); |
| 240 print(row { "port"; "Commands to manage ports the server is listening on" }) | 336 |
| 241 print(row { "dns"; "Commands to manage and inspect the internal DNS resolver" }) | 337 print [[In addition to info about commands, the following general topics are available:]] |
| 242 print(row { "xmpp"; "Commands for sending XMPP stanzas" }) | 338 |
| 243 print(row { "debug"; "Commands for debugging the server" }) | 339 print(""); |
| 244 print(row { "watch"; "Commands for watching live logs from the server" }) | 340 for topic_name, topic in it.sorted_pairs(help_topics) do |
| 245 print(row { "config"; "Reloading the configuration, etc." }) | 341 print(topic_name .. " - "..topic.desc); |
| 246 print(row { "columns"; "Information about customizing session listings" }) | |
| 247 print(row { "console"; "Help regarding the console itself" }) | |
| 248 elseif section == "c2s" then | |
| 249 print [[c2s:show(jid, columns) - Show all client sessions with the specified JID (or all if no JID given)]] | |
| 250 print [[c2s:show_tls(jid) - Show TLS cipher info for encrypted sessions]] | |
| 251 print [[c2s:count() - Count sessions without listing them]] | |
| 252 print [[c2s:close(jid) - Close all sessions for the specified JID]] | |
| 253 print [[c2s:closeall() - Close all active c2s connections ]] | |
| 254 elseif section == "s2s" then | |
| 255 print [[s2s:show(domain, columns) - Show all s2s connections for the given domain (or all if no domain given)]] | |
| 256 print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] | |
| 257 print [[s2s:close(from, to) - Close a connection from one domain to another]] | |
| 258 print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] | |
| 259 elseif section == "http" then | |
| 260 print [[http:list(hosts) - Show HTTP endpoints]] | |
| 261 elseif section == "module" then | |
| 262 print [[module:info(module, host) - Show information about a loaded module]] | |
| 263 print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] | |
| 264 print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] | |
| 265 print [[module:unload(module, host) - The same, but just unloads the module from memory]] | |
| 266 print [[module:list(host) - List the modules loaded on the specified host]] | |
| 267 elseif section == "host" then | |
| 268 print [[host:activate(hostname) - Activates the specified host]] | |
| 269 print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]] | |
| 270 print [[host:list() - List the currently-activated hosts]] | |
| 271 elseif section == "user" then | |
| 272 print [[user:create(jid, password, role) - Create the specified user account]] | |
| 273 print [[user:password(jid, password) - Set the password for the specified user account]] | |
| 274 print [[user:roles(jid, host) - Show current roles for an user]] | |
| 275 print [[user:setrole(jid, host, role) - Set primary role of a user (see 'help roles')]] | |
| 276 print [[user:addrole(jid, host, role) - Add a secondary role to a user]] | |
| 277 print [[user:delrole(jid, host, role) - Remove a secondary role from a user]] | |
| 278 print [[user:disable(jid) - Disable the specified user account, preventing login]] | |
| 279 print [[user:enable(jid) - Enable the specified user account, restoring login access]] | |
| 280 print [[user:delete(jid) - Permanently remove the specified user account]] | |
| 281 print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]] | |
| 282 elseif section == "roles" then | |
| 283 print [[Roles may grant access or restrict users from certain operations]] | |
| 284 print [[Built-in roles are:]] | |
| 285 print [[ prosody:guest - Guest/anonymous user]] | |
| 286 print [[ prosody:registered - Registered user]] | |
| 287 print [[ prosody:member - Provisioned user]] | |
| 288 print [[ prosody:admin - Host administrator]] | |
| 289 print [[ prosody:operator - Server administrator]] | |
| 290 print [[]] | |
| 291 print [[Roles can be assigned using the user management commands (see 'help user').]] | |
| 292 elseif section == "muc" then | |
| 293 -- TODO `muc:room():foo()` commands | |
| 294 print [[muc:create(roomjid, { config }) - Create the specified MUC room with the given config]] | |
| 295 print [[muc:list(host) - List rooms on the specified MUC component]] | |
| 296 print [[muc:room(roomjid) - Reference the specified MUC room to access MUC API methods]] | |
| 297 print [[muc:occupants(roomjid, filter) - List room occupants, optionally filtered on substring or role]] | |
| 298 print [[muc:affiliations(roomjid, filter) - List affiliated members of the room, optionally filtered on substring or affiliation]] | |
| 299 elseif section == "server" then | |
| 300 print [[server:version() - Show the server's version number]] | |
| 301 print [[server:uptime() - Show how long the server has been running]] | |
| 302 print [[server:memory() - Show details about the server's memory usage]] | |
| 303 print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]] | |
| 304 elseif section == "port" then | |
| 305 print [[port:list() - Lists all network ports prosody currently listens on]] | |
| 306 print [[port:close(port, interface) - Close a port]] | |
| 307 elseif section == "dns" then | |
| 308 print [[dns:lookup(name, type, class) - Do a DNS lookup]] | |
| 309 print [[dns:addnameserver(nameserver) - Add a nameserver to the list]] | |
| 310 print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]] | |
| 311 print [[dns:purge() - Clear the DNS cache]] | |
| 312 print [[dns:cache() - Show cached records]] | |
| 313 elseif section == "xmpp" then | |
| 314 print [[xmpp:ping(localhost, remotehost) -- Sends a ping to a remote XMPP server and reports the response]] | |
| 315 elseif section == "config" then | |
| 316 print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] | |
| 317 print [[config:get([host,] option) - Show the value of a config option.]] | |
| 318 print [[config:set([host,] option, value) - Update the value of a config option without writing to the config file.]] | |
| 319 elseif section == "stats" then -- luacheck: ignore 542 | |
| 320 print [[stats:show(pattern) - Show internal statistics, optionally filtering by name with a pattern]] | |
| 321 print [[stats:show():cfgraph() - Show a cumulative frequency graph]] | |
| 322 print [[stats:show():histogram() - Show a histogram of selected metric]] | |
| 323 elseif section == "debug" then | |
| 324 print [[debug:async() - Show information about pending asynchronous tasks]] | |
| 325 print [[debug:logevents(host) - Enable logging of fired events on host]] | |
| 326 print [[debug:events(host, event) - Show registered event handlers]] | |
| 327 print [[debug:timers() - Show information about scheduled timers]] | |
| 328 elseif section == "watch" then | |
| 329 print [[watch:log() - Follow debug logs]] | |
| 330 print [[watch:stanzas(target, filter) - Watch live stanzas matching the specified target and filter]] | |
| 331 elseif section == "console" then | |
| 332 print [[Hey! Welcome to Prosody's admin console.]] | |
| 333 print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] | |
| 334 print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]] | |
| 335 print [[so you may have trouble using the arrow keys, etc. depending on your system.]] | |
| 336 print [[]] | |
| 337 print [[For now we offer a couple of handy shortcuts:]] | |
| 338 print [[!! - Repeat the last command]] | |
| 339 print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']] | |
| 340 print [[]] | |
| 341 print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]] | |
| 342 print [[you can prefix a command with > to escape the console sandbox, and access everything in]] | |
| 343 print [[the running server. Great fun, but be careful not to break anything :)]] | |
| 344 elseif section == "columns" then | |
| 345 print [[The columns shown by c2s:show() and s2s:show() can be customizied via the]] | |
| 346 print [['columns' argument as described here.]] | |
| 347 print [[]] | |
| 348 print [[Columns can be specified either as "id jid ipv" or as {"id", "jid", "ipv"}.]] | |
| 349 print [[Available columns are:]] | |
| 350 local meta_columns = { | |
| 351 { title = "ID"; width = 5 }; | |
| 352 { title = "Column Title"; width = 12 }; | |
| 353 { title = "Description"; width = 12 }; | |
| 354 }; | |
| 355 -- auto-adjust widths | |
| 356 for column, spec in pairs(available_columns) do | |
| 357 meta_columns[1].width = math.max(meta_columns[1].width or 0, #column); | |
| 358 meta_columns[2].width = math.max(meta_columns[2].width or 0, #(spec.title or "")); | |
| 359 meta_columns[3].width = math.max(meta_columns[3].width or 0, #(spec.description or "")); | |
| 360 end | |
| 361 local row = format_table(meta_columns, session.width) | |
| 362 print(row()); | |
| 363 for column, spec in iterators.sorted_pairs(available_columns) do | |
| 364 print(row({ column, spec.title, spec.description })); | |
| 365 end | |
| 366 print [[]] | |
| 367 print [[Most fields on the internal session structures can also be used as columns]] | |
| 368 -- Also, you can pass a table column specification directly, with mapper callback and all | |
| 369 end | 342 end |
| 370 end | 343 end |
| 371 | 344 |
| 372 -- Session environment -- | 345 -- Session environment -- |
| 373 -- Anything in def_env will be accessible within the session as a global variable | 346 -- Anything in def_env will be accessible within the session as a global variable |
| 377 preset = "pretty"; | 350 preset = "pretty"; |
| 378 maxdepth = 2; | 351 maxdepth = 2; |
| 379 table_iterator = "pairs"; | 352 table_iterator = "pairs"; |
| 380 }) | 353 }) |
| 381 | 354 |
| 382 def_env.output = {}; | 355 def_env.output = new_section("Configure admin console output"); |
| 383 function def_env.output:configure(opts) | 356 function def_env.output:configure(opts) |
| 384 if type(opts) ~= "table" then | 357 if type(opts) ~= "table" then |
| 385 opts = { preset = opts }; | 358 opts = { preset = opts }; |
| 386 end | 359 end |
| 387 if not opts.fallback then | 360 if not opts.fallback then |
| 399 opts.table_iterator = nil; -- rawpairs is the default | 372 opts.table_iterator = nil; -- rawpairs is the default |
| 400 end | 373 end |
| 401 self.session.serialize = serialization.new(opts); | 374 self.session.serialize = serialization.new(opts); |
| 402 end | 375 end |
| 403 | 376 |
| 404 def_env.server = {}; | 377 def_env.help = setmetatable({}, { |
| 378 help = { | |
| 379 desc = "Show this help about available commands"; | |
| 380 commands = {}; | |
| 381 }; | |
| 382 __index = function (_, section_name) | |
| 383 return function (self) | |
| 384 local print = self.session.print; | |
| 385 local section_mt = getmetatable(def_env[section_name]); | |
| 386 local section_help = section_mt and section_mt.help; | |
| 387 | |
| 388 local c = 0; | |
| 389 | |
| 390 if section_help then | |
| 391 print("Help: "..section_name); | |
| 392 if section_help.desc then | |
| 393 print(section_help.desc); | |
| 394 end | |
| 395 print(("-"):rep(#(section_help.desc or section_name))); | |
| 396 print(""); | |
| 397 | |
| 398 if section_help.content then | |
| 399 print(section_help.content); | |
| 400 print(""); | |
| 401 end | |
| 402 | |
| 403 for command, command_help in it.sorted_pairs(section_help.commands or {}) do | |
| 404 c = c + 1; | |
| 405 local args = command_help.args:pluck("name"):concat(", "); | |
| 406 local desc = command_help.desc or command_help.module and ("Provided by mod_"..command_help.module) or ""; | |
| 407 print(("%s:%s(%s) - %s"):format(section_name, command, args, desc)); | |
| 408 end | |
| 409 elseif help_topics[section_name] then | |
| 410 local topic = help_topics[section_name]; | |
| 411 if type(topic.content) == "function" then | |
| 412 topic.content(self, print); | |
| 413 else | |
| 414 print(topic.content); | |
| 415 end | |
| 416 print(""); | |
| 417 return true, "Showing help topic '"..section_name.."'"; | |
| 418 else | |
| 419 print("Unknown topic: "..section_name); | |
| 420 end | |
| 421 print(""); | |
| 422 return true, ("%d command(s) listed"):format(c); | |
| 423 end; | |
| 424 end; | |
| 425 }); | |
| 426 | |
| 427 def_env.server = new_section("Uptime, version, shutting down, etc."); | |
| 405 | 428 |
| 406 function def_env.server:insane_reload() | 429 function def_env.server:insane_reload() |
| 407 prosody.unlock_globals(); | 430 prosody.unlock_globals(); |
| 408 dofile "prosody" | 431 dofile "prosody" |
| 409 prosody = _G.prosody; | 432 prosody = _G.prosody; |
| 410 return true, "Server reloaded"; | 433 return true, "Server reloaded"; |
| 411 end | 434 end |
| 412 | 435 |
| 436 describe_command [[server:version() - Show the server's version number]] | |
| 413 function def_env.server:version() | 437 function def_env.server:version() |
| 414 return true, tostring(prosody.version or "unknown"); | 438 return true, tostring(prosody.version or "unknown"); |
| 415 end | 439 end |
| 416 | 440 |
| 441 describe_command [[server:uptime() - Show how long the server has been running]] | |
| 417 function def_env.server:uptime() | 442 function def_env.server:uptime() |
| 418 local t = os.time()-prosody.start_time; | 443 local t = os.time()-prosody.start_time; |
| 419 local seconds = t%60; | 444 local seconds = t%60; |
| 420 t = (t - seconds)/60; | 445 t = (t - seconds)/60; |
| 421 local minutes = t%60; | 446 local minutes = t%60; |
| 426 return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", | 451 return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", |
| 427 days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", | 452 days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", |
| 428 minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); | 453 minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); |
| 429 end | 454 end |
| 430 | 455 |
| 456 describe_command [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]] | |
| 431 function def_env.server:shutdown(reason, code) | 457 function def_env.server:shutdown(reason, code) |
| 432 prosody.shutdown(reason, code); | 458 prosody.shutdown(reason, code); |
| 433 return true, "Shutdown initiated"; | 459 return true, "Shutdown initiated"; |
| 434 end | 460 end |
| 435 | 461 |
| 436 local function human(kb) | 462 local function human(kb) |
| 437 return format_number(kb*1024, "B", "b"); | 463 return format_number(kb*1024, "B", "b"); |
| 438 end | 464 end |
| 439 | 465 |
| 466 describe_command [[server:memory() - Show details about the server's memory usage]] | |
| 440 function def_env.server:memory() | 467 function def_env.server:memory() |
| 441 if not has_pposix or not pposix.meminfo then | 468 if not has_pposix or not pposix.meminfo then |
| 442 return true, "Lua is using "..human(collectgarbage("count")); | 469 return true, "Lua is using "..human(collectgarbage("count")); |
| 443 end | 470 end |
| 444 local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); | 471 local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); |
| 447 print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); | 474 print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); |
| 448 print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); | 475 print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); |
| 449 return true, "OK"; | 476 return true, "OK"; |
| 450 end | 477 end |
| 451 | 478 |
| 452 def_env.module = {}; | 479 def_env.module = new_section("Commands to load/reload/unload modules/plugins"); |
| 453 | 480 |
| 454 local function get_hosts_set(hosts) | 481 local function get_hosts_set(hosts) |
| 455 if type(hosts) == "table" then | 482 if type(hosts) == "table" then |
| 456 if hosts[1] then | 483 if hosts[1] then |
| 457 return set.new(hosts); | 484 return set.new(hosts); |
| 493 hosts_set:add("*"); | 520 hosts_set:add("*"); |
| 494 end | 521 end |
| 495 return hosts_set; | 522 return hosts_set; |
| 496 end | 523 end |
| 497 | 524 |
| 525 describe_command [[module:info(module, host) - Show information about a loaded module]] | |
| 498 function def_env.module:info(name, hosts) | 526 function def_env.module:info(name, hosts) |
| 499 if not name then | 527 if not name then |
| 500 return nil, "module name expected"; | 528 return nil, "module name expected"; |
| 501 end | 529 end |
| 502 local print = self.session.print; | 530 local print = self.session.print; |
| 597 end | 625 end |
| 598 end | 626 end |
| 599 return true; | 627 return true; |
| 600 end | 628 end |
| 601 | 629 |
| 630 describe_command [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] | |
| 602 function def_env.module:load(name, hosts) | 631 function def_env.module:load(name, hosts) |
| 603 hosts = get_hosts_with_module(hosts); | 632 hosts = get_hosts_with_module(hosts); |
| 604 | 633 |
| 605 -- Load the module for each host | 634 -- Load the module for each host |
| 606 local ok, err, count, mod = true, nil, 0; | 635 local ok, err, count, mod = true, nil, 0; |
| 630 end | 659 end |
| 631 | 660 |
| 632 return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); | 661 return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); |
| 633 end | 662 end |
| 634 | 663 |
| 664 describe_command [[module:unload(module, host) - The same, but just unloads the module from memory]] | |
| 635 function def_env.module:unload(name, hosts) | 665 function def_env.module:unload(name, hosts) |
| 636 hosts = get_hosts_with_module(hosts, name); | 666 hosts = get_hosts_with_module(hosts, name); |
| 637 | 667 |
| 638 -- Unload the module for each host | 668 -- Unload the module for each host |
| 639 local ok, err, count = true, nil, 0; | 669 local ok, err, count = true, nil, 0; |
| 662 if a == "*" then return true | 692 if a == "*" then return true |
| 663 elseif b == "*" then return false | 693 elseif b == "*" then return false |
| 664 else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end | 694 else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end |
| 665 end | 695 end |
| 666 | 696 |
| 697 describe_command [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] | |
| 667 function def_env.module:reload(name, hosts) | 698 function def_env.module:reload(name, hosts) |
| 668 hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts) | 699 hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts) |
| 669 | 700 |
| 670 -- Reload the module for each host | 701 -- Reload the module for each host |
| 671 local ok, err, count = true, nil, 0; | 702 local ok, err, count = true, nil, 0; |
| 685 end | 716 end |
| 686 end | 717 end |
| 687 return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); | 718 return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); |
| 688 end | 719 end |
| 689 | 720 |
| 721 describe_command [[module:list(host) - List the modules loaded on the specified host]] | |
| 690 function def_env.module:list(hosts) | 722 function def_env.module:list(hosts) |
| 691 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); | 723 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); |
| 692 | 724 |
| 693 local print = self.session.print; | 725 local print = self.session.print; |
| 694 for _, host in ipairs(hosts) do | 726 for _, host in ipairs(hosts) do |
| 711 end | 743 end |
| 712 end | 744 end |
| 713 end | 745 end |
| 714 end | 746 end |
| 715 | 747 |
| 716 def_env.config = {}; | 748 def_env.config = new_section("Reloading the configuration, etc."); |
| 749 | |
| 717 function def_env.config:load(filename, format) | 750 function def_env.config:load(filename, format) |
| 718 local config_load = require "prosody.core.configmanager".load; | 751 local config_load = require "prosody.core.configmanager".load; |
| 719 local ok, err = config_load(filename, format); | 752 local ok, err = config_load(filename, format); |
| 720 if not ok then | 753 if not ok then |
| 721 return false, err or "Unknown error loading config"; | 754 return false, err or "Unknown error loading config"; |
| 722 end | 755 end |
| 723 return true, "Config loaded"; | 756 return true, "Config loaded"; |
| 724 end | 757 end |
| 725 | 758 |
| 759 describe_command [[config:get([host,] option) - Show the value of a config option.]] | |
| 726 function def_env.config:get(host, key) | 760 function def_env.config:get(host, key) |
| 727 if key == nil then | 761 if key == nil then |
| 728 host, key = "*", host; | 762 host, key = "*", host; |
| 729 end | 763 end |
| 730 local config_get = require "prosody.core.configmanager".get | 764 local config_get = require "prosody.core.configmanager".get |
| 731 return true, serialize_config(config_get(host, key)); | 765 return true, serialize_config(config_get(host, key)); |
| 732 end | 766 end |
| 733 | 767 |
| 768 describe_command [[config:set([host,] option, value) - Update the value of a config option without writing to the config file.]] | |
| 734 function def_env.config:set(host, key, value) | 769 function def_env.config:set(host, key, value) |
| 735 if host ~= "*" and not prosody.hosts[host] then | 770 if host ~= "*" and not prosody.hosts[host] then |
| 736 host, key, value = "*", host, key; | 771 host, key, value = "*", host, key; |
| 737 end | 772 end |
| 738 return require "prosody.core.configmanager".set(host, key, value); | 773 return require "prosody.core.configmanager".set(host, key, value); |
| 739 end | 774 end |
| 740 | 775 |
| 776 describe_command [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] | |
| 741 function def_env.config:reload() | 777 function def_env.config:reload() |
| 742 local ok, err = prosody.reload_config(); | 778 local ok, err = prosody.reload_config(); |
| 743 return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); | 779 return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); |
| 744 end | 780 end |
| 745 | 781 |
| 746 def_env.c2s = {}; | 782 def_env.c2s = new_section("Commands to manage local client-to-server sessions"); |
| 747 | 783 |
| 748 local function get_jid(session) | 784 local function get_jid(session) |
| 749 if session.username then | 785 if session.username then |
| 750 return session.full_jid or jid_join(session.username, session.host, session.resource); | 786 return session.full_jid or jid_join(session.username, session.host, session.resource); |
| 751 end | 787 end |
| 778 get_c2s():sort(_sort_by_jid):map(function (session) | 814 get_c2s():sort(_sort_by_jid):map(function (session) |
| 779 callback(get_jid(session), session) | 815 callback(get_jid(session), session) |
| 780 end); | 816 end); |
| 781 end | 817 end |
| 782 | 818 |
| 819 describe_command [[c2s:count() - Count sessions without listing them]] | |
| 783 function def_env.c2s:count() | 820 function def_env.c2s:count() |
| 784 local c2s = get_c2s(); | 821 local c2s = get_c2s(); |
| 785 return true, "Total: ".. #c2s .." clients"; | 822 return true, "Total: ".. #c2s .." clients"; |
| 786 end | 823 end |
| 787 | 824 |
| 1042 end | 1079 end |
| 1043 | 1080 |
| 1044 return columns; | 1081 return columns; |
| 1045 end | 1082 end |
| 1046 | 1083 |
| 1084 describe_command [[c2s:show(jid, columns) - Show all client sessions with the specified JID (or all if no JID given)]] | |
| 1047 function def_env.c2s:show(match_jid, colspec) | 1085 function def_env.c2s:show(match_jid, colspec) |
| 1048 local print = self.session.print; | 1086 local print = self.session.print; |
| 1049 local columns = get_colspec(colspec, { "id"; "jid"; "role"; "ipv"; "status"; "secure"; "smacks"; "csi" }); | 1087 local columns = get_colspec(colspec, { "id"; "jid"; "role"; "ipv"; "status"; "secure"; "smacks"; "csi" }); |
| 1050 local row = format_table(columns, self.session.width); | 1088 local row = format_table(columns, self.session.width); |
| 1051 | 1089 |
| 1082 return true, ("%d out of %d c2s sessions shown"):format(shown_count, total_count); | 1120 return true, ("%d out of %d c2s sessions shown"):format(shown_count, total_count); |
| 1083 end | 1121 end |
| 1084 return true, ("%d c2s sessions shown"):format(total_count); | 1122 return true, ("%d c2s sessions shown"):format(total_count); |
| 1085 end | 1123 end |
| 1086 | 1124 |
| 1125 describe_command [[c2s:show_tls(jid) - Show TLS cipher info for encrypted sessions]] | |
| 1087 function def_env.c2s:show_tls(match_jid) | 1126 function def_env.c2s:show_tls(match_jid) |
| 1088 return self:show(match_jid, { "jid"; "id"; "secure"; "encryption" }); | 1127 return self:show(match_jid, { "jid"; "id"; "secure"; "encryption" }); |
| 1089 end | 1128 end |
| 1090 | 1129 |
| 1091 local function build_reason(text, condition) | 1130 local function build_reason(text, condition) |
| 1095 condition = condition or "undefined-condition", | 1134 condition = condition or "undefined-condition", |
| 1096 }; | 1135 }; |
| 1097 end | 1136 end |
| 1098 end | 1137 end |
| 1099 | 1138 |
| 1139 describe_command [[c2s:close(jid) - Close all sessions for the specified JID]] | |
| 1100 function def_env.c2s:close(match_jid, text, condition) | 1140 function def_env.c2s:close(match_jid, text, condition) |
| 1101 local count = 0; | 1141 local count = 0; |
| 1102 show_c2s(function (jid, session) | 1142 show_c2s(function (jid, session) |
| 1103 if jid == match_jid or jid_bare(jid) == match_jid then | 1143 if jid == match_jid or jid_bare(jid) == match_jid then |
| 1104 count = count + 1; | 1144 count = count + 1; |
| 1106 end | 1146 end |
| 1107 end); | 1147 end); |
| 1108 return true, "Total: "..count.." sessions closed"; | 1148 return true, "Total: "..count.." sessions closed"; |
| 1109 end | 1149 end |
| 1110 | 1150 |
| 1151 describe_command [[c2s:closeall() - Close all active c2s connections ]] | |
| 1111 function def_env.c2s:closeall(text, condition) | 1152 function def_env.c2s:closeall(text, condition) |
| 1112 local count = 0; | 1153 local count = 0; |
| 1113 --luacheck: ignore 212/jid | 1154 --luacheck: ignore 212/jid |
| 1114 show_c2s(function (jid, session) | 1155 show_c2s(function (jid, session) |
| 1115 count = count + 1; | 1156 count = count + 1; |
| 1117 end); | 1158 end); |
| 1118 return true, "Total: "..count.." sessions closed"; | 1159 return true, "Total: "..count.." sessions closed"; |
| 1119 end | 1160 end |
| 1120 | 1161 |
| 1121 | 1162 |
| 1122 def_env.s2s = {}; | 1163 def_env.s2s = new_section("Commands to manage sessions between this server and others"); |
| 1164 | |
| 1123 local function _sort_s2s(a, b) | 1165 local function _sort_s2s(a, b) |
| 1124 local a_local, a_remote = get_s2s_hosts(a); | 1166 local a_local, a_remote = get_s2s_hosts(a); |
| 1125 local b_local, b_remote = get_s2s_hosts(b); | 1167 local b_local, b_remote = get_s2s_hosts(b); |
| 1126 if (a_local or "") == (b_local or "") then return _sort_hosts(a_remote or "", b_remote or ""); end | 1168 if (a_local or "") == (b_local or "") then return _sort_hosts(a_remote or "", b_remote or ""); end |
| 1127 return _sort_hosts(a_local or "", b_local or ""); | 1169 return _sort_hosts(a_local or "", b_local or ""); |
| 1142 return match_wildcard(match_jid, host) or match_wildcard(match_jid, remote); | 1184 return match_wildcard(match_jid, host) or match_wildcard(match_jid, remote); |
| 1143 end | 1185 end |
| 1144 return false; | 1186 return false; |
| 1145 end | 1187 end |
| 1146 | 1188 |
| 1189 describe_command [[s2s:show(domain, columns) - Show all s2s connections for the given domain (or all if no domain given)]] | |
| 1147 function def_env.s2s:show(match_jid, colspec) | 1190 function def_env.s2s:show(match_jid, colspec) |
| 1148 local print = self.session.print; | 1191 local print = self.session.print; |
| 1149 local columns = get_colspec(colspec, { "id"; "host"; "dir"; "remote"; "ipv"; "secure"; "s2s_sasl"; "dialback" }); | 1192 local columns = get_colspec(colspec, { "id"; "host"; "dir"; "remote"; "ipv"; "secure"; "s2s_sasl"; "dialback" }); |
| 1150 local row = format_table(columns, self.session.width); | 1193 local row = format_table(columns, self.session.width); |
| 1151 | 1194 |
| 1182 return true, ("%d out of %d s2s connections shown"):format(shown_count, total_count); | 1225 return true, ("%d out of %d s2s connections shown"):format(shown_count, total_count); |
| 1183 end | 1226 end |
| 1184 return true, ("%d s2s connections shown"):format(total_count); | 1227 return true, ("%d s2s connections shown"):format(total_count); |
| 1185 end | 1228 end |
| 1186 | 1229 |
| 1230 describe_command [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] | |
| 1187 function def_env.s2s:show_tls(match_jid) | 1231 function def_env.s2s:show_tls(match_jid) |
| 1188 return self:show(match_jid, { "id"; "host"; "dir"; "remote"; "secure"; "encryption"; "cert" }); | 1232 return self:show(match_jid, { "id"; "host"; "dir"; "remote"; "secure"; "encryption"; "cert" }); |
| 1189 end | 1233 end |
| 1190 | 1234 |
| 1191 local function print_subject(print, subject) | 1235 local function print_subject(print, subject) |
| 1304 return ("Showing "..n_certs.." certificate" | 1348 return ("Showing "..n_certs.." certificate" |
| 1305 ..(n_certs==1 and "" or "s") | 1349 ..(n_certs==1 and "" or "s") |
| 1306 .." presented by "..domain.."."); | 1350 .." presented by "..domain.."."); |
| 1307 end | 1351 end |
| 1308 | 1352 |
| 1353 describe_command [[s2s:close(from, to) - Close a connection from one domain to another]] | |
| 1309 function def_env.s2s:close(from, to, text, condition) | 1354 function def_env.s2s:close(from, to, text, condition) |
| 1310 local print, count = self.session.print, 0; | 1355 local print, count = self.session.print, 0; |
| 1311 local s2s_sessions = module:shared"/*/s2s/sessions"; | 1356 local s2s_sessions = module:shared"/*/s2s/sessions"; |
| 1312 | 1357 |
| 1313 local match_id; | 1358 local match_id; |
| 1328 end | 1373 end |
| 1329 end | 1374 end |
| 1330 return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); | 1375 return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); |
| 1331 end | 1376 end |
| 1332 | 1377 |
| 1378 describe_command [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] | |
| 1333 function def_env.s2s:closeall(host, text, condition) | 1379 function def_env.s2s:closeall(host, text, condition) |
| 1334 local count = 0; | 1380 local count = 0; |
| 1335 local s2s_sessions = module:shared"/*/s2s/sessions"; | 1381 local s2s_sessions = module:shared"/*/s2s/sessions"; |
| 1336 for _,session in pairs(s2s_sessions) do | 1382 for _,session in pairs(s2s_sessions) do |
| 1337 if not host or host == "*" or match_s2s_jid(session, host) then | 1383 if not host or host == "*" or match_s2s_jid(session, host) then |
| 1341 end | 1387 end |
| 1342 if count == 0 then return false, "No sessions to close."; | 1388 if count == 0 then return false, "No sessions to close."; |
| 1343 else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end | 1389 else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end |
| 1344 end | 1390 end |
| 1345 | 1391 |
| 1346 def_env.host = {}; def_env.hosts = def_env.host; | 1392 def_env.host = new_section("Commands to activate, deactivate and list virtual hosts"); |
| 1347 | 1393 |
| 1394 describe_command [[host:activate(hostname) - Activates the specified host]] | |
| 1348 function def_env.host:activate(hostname, config) | 1395 function def_env.host:activate(hostname, config) |
| 1349 return hostmanager.activate(hostname, config); | 1396 return hostmanager.activate(hostname, config); |
| 1350 end | 1397 end |
| 1398 | |
| 1399 describe_command [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]] | |
| 1351 function def_env.host:deactivate(hostname, reason) | 1400 function def_env.host:deactivate(hostname, reason) |
| 1352 return hostmanager.deactivate(hostname, reason); | 1401 return hostmanager.deactivate(hostname, reason); |
| 1353 end | 1402 end |
| 1354 | 1403 |
| 1404 describe_command [[host:list() - List the currently-activated hosts]] | |
| 1355 function def_env.host:list() | 1405 function def_env.host:list() |
| 1356 local print = self.session.print; | 1406 local print = self.session.print; |
| 1357 local i = 0; | 1407 local i = 0; |
| 1358 local host_type; | 1408 local host_type; |
| 1359 for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do | 1409 for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do |
| 1370 end | 1420 end |
| 1371 end | 1421 end |
| 1372 return true, i.." hosts"; | 1422 return true, i.." hosts"; |
| 1373 end | 1423 end |
| 1374 | 1424 |
| 1375 def_env.port = {}; | 1425 def_env.port = new_section("Commands to manage ports the server is listening on"); |
| 1376 | 1426 |
| 1427 describe_command [[port:list() - Lists all network ports prosody currently listens on]] | |
| 1377 function def_env.port:list() | 1428 function def_env.port:list() |
| 1378 local print = self.session.print; | 1429 local print = self.session.print; |
| 1379 local services = portmanager.get_active_services().data; | 1430 local services = portmanager.get_active_services().data; |
| 1380 local n_services, n_ports = 0, 0; | 1431 local n_services, n_ports = 0, 0; |
| 1381 for service, interfaces in iterators.sorted_pairs(services) do | 1432 for service, interfaces in iterators.sorted_pairs(services) do |
| 1390 print(service..": "..table.concat(ports_list, ", ")); | 1441 print(service..": "..table.concat(ports_list, ", ")); |
| 1391 end | 1442 end |
| 1392 return true, n_services.." services listening on "..n_ports.." ports"; | 1443 return true, n_services.." services listening on "..n_ports.." ports"; |
| 1393 end | 1444 end |
| 1394 | 1445 |
| 1446 describe_command [[port:close(port, interface) - Close a port]] | |
| 1395 function def_env.port:close(close_port, close_interface) | 1447 function def_env.port:close(close_port, close_interface) |
| 1396 close_port = assert(tonumber(close_port), "Invalid port number"); | 1448 close_port = assert(tonumber(close_port), "Invalid port number"); |
| 1397 local n_closed = 0; | 1449 local n_closed = 0; |
| 1398 local services = portmanager.get_active_services().data; | 1450 local services = portmanager.get_active_services().data; |
| 1399 for service, interfaces in pairs(services) do -- luacheck: ignore 213 | 1451 for service, interfaces in pairs(services) do -- luacheck: ignore 213 |
| 1412 end | 1464 end |
| 1413 end | 1465 end |
| 1414 return true, "Closed "..n_closed.." ports"; | 1466 return true, "Closed "..n_closed.." ports"; |
| 1415 end | 1467 end |
| 1416 | 1468 |
| 1417 def_env.muc = {}; | 1469 def_env.muc = new_section("Commands to create, list and manage chat rooms"); |
| 1418 | 1470 |
| 1419 local console_room_mt = { | 1471 local console_room_mt = { |
| 1420 __index = function (self, k) return self.room[k]; end; | 1472 __index = function (self, k) return self.room[k]; end; |
| 1421 __tostring = function (self) | 1473 __tostring = function (self) |
| 1422 return "MUC room <"..self.room.jid..">"; | 1474 return "MUC room <"..self.room.jid..">"; |
| 1445 return room_obj; | 1497 return room_obj; |
| 1446 end | 1498 end |
| 1447 | 1499 |
| 1448 local muc_util = module:require"muc/util"; | 1500 local muc_util = module:require"muc/util"; |
| 1449 | 1501 |
| 1502 describe_command [[muc:create(roomjid, { config }) - Create the specified MUC room with the given config]] | |
| 1450 function def_env.muc:create(room_jid, config) | 1503 function def_env.muc:create(room_jid, config) |
| 1451 local room_name, host = check_muc(room_jid); | 1504 local room_name, host = check_muc(room_jid); |
| 1452 if not room_name then | 1505 if not room_name then |
| 1453 return room_name, host; | 1506 return room_name, host; |
| 1454 end | 1507 end |
| 1456 if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end | 1509 if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end |
| 1457 if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end | 1510 if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end |
| 1458 return prosody.hosts[host].modules.muc.create_room(room_jid, config); | 1511 return prosody.hosts[host].modules.muc.create_room(room_jid, config); |
| 1459 end | 1512 end |
| 1460 | 1513 |
| 1514 describe_command [[muc:room(roomjid) - Reference the specified MUC room to access MUC API methods]] | |
| 1461 function def_env.muc:room(room_jid) | 1515 function def_env.muc:room(room_jid) |
| 1462 local room_obj, err = get_muc(room_jid); | 1516 local room_obj, err = get_muc(room_jid); |
| 1463 if not room_obj then | 1517 if not room_obj then |
| 1464 return room_obj, err; | 1518 return room_obj, err; |
| 1465 end | 1519 end |
| 1466 return setmetatable({ room = room_obj }, console_room_mt); | 1520 return setmetatable({ room = room_obj }, console_room_mt); |
| 1467 end | 1521 end |
| 1468 | 1522 |
| 1523 describe_command [[muc:list(host) - List rooms on the specified MUC component]] | |
| 1469 function def_env.muc:list(host) | 1524 function def_env.muc:list(host) |
| 1470 local host_session = prosody.hosts[host]; | 1525 local host_session = prosody.hosts[host]; |
| 1471 if not host_session or not host_session.modules.muc then | 1526 if not host_session or not host_session.modules.muc then |
| 1472 return nil, "Please supply the address of a local MUC component"; | 1527 return nil, "Please supply the address of a local MUC component"; |
| 1473 end | 1528 end |
| 1478 c = c + 1; | 1533 c = c + 1; |
| 1479 end | 1534 end |
| 1480 return true, c.." rooms"; | 1535 return true, c.." rooms"; |
| 1481 end | 1536 end |
| 1482 | 1537 |
| 1538 describe_command [[muc:occupants(roomjid, filter) - List room occupants, optionally filtered on substring or role]] | |
| 1483 function def_env.muc:occupants(room_jid, filter) | 1539 function def_env.muc:occupants(room_jid, filter) |
| 1484 local room_obj, err = get_muc(room_jid); | 1540 local room_obj, err = get_muc(room_jid); |
| 1485 if not room_obj then | 1541 if not room_obj then |
| 1486 return room_obj, err; | 1542 return room_obj, err; |
| 1487 end | 1543 end |
| 1522 else | 1578 else |
| 1523 return true, ("%d out of %d occupant%s listed"):format(displayed, total, total ~= 1 and "s" or "") | 1579 return true, ("%d out of %d occupant%s listed"):format(displayed, total, total ~= 1 and "s" or "") |
| 1524 end | 1580 end |
| 1525 end | 1581 end |
| 1526 | 1582 |
| 1583 describe_command [[muc:affiliations(roomjid, filter) - List affiliated members of the room, optionally filtered on substring or affiliation]] | |
| 1527 function def_env.muc:affiliations(room_jid, filter) | 1584 function def_env.muc:affiliations(room_jid, filter) |
| 1528 local room_obj, err = get_muc(room_jid); | 1585 local room_obj, err = get_muc(room_jid); |
| 1529 if not room_obj then | 1586 if not room_obj then |
| 1530 return room_obj, err; | 1587 return room_obj, err; |
| 1531 end | 1588 end |
| 1574 end | 1631 end |
| 1575 end | 1632 end |
| 1576 | 1633 |
| 1577 local um = require"prosody.core.usermanager"; | 1634 local um = require"prosody.core.usermanager"; |
| 1578 | 1635 |
| 1579 def_env.user = {}; | 1636 def_env.user = new_section("Commands to create and delete users, and change their passwords"); |
| 1637 | |
| 1638 describe_command [[user:create(jid, password, role) - Create the specified user account]] | |
| 1580 function def_env.user:create(jid, password, role) | 1639 function def_env.user:create(jid, password, role) |
| 1581 local username, host = jid_split(jid); | 1640 local username, host = jid_split(jid); |
| 1582 if not prosody.hosts[host] then | 1641 if not prosody.hosts[host] then |
| 1583 return nil, "No such host: "..host; | 1642 return nil, "No such host: "..host; |
| 1584 elseif um.user_exists(username, host) then | 1643 elseif um.user_exists(username, host) then |
| 1595 end | 1654 end |
| 1596 | 1655 |
| 1597 return true, ("Created %s with role '%s'"):format(jid, role); | 1656 return true, ("Created %s with role '%s'"):format(jid, role); |
| 1598 end | 1657 end |
| 1599 | 1658 |
| 1659 describe_command [[user:disable(jid) - Disable the specified user account, preventing login]] | |
| 1600 function def_env.user:disable(jid) | 1660 function def_env.user:disable(jid) |
| 1601 local username, host = jid_split(jid); | 1661 local username, host = jid_split(jid); |
| 1602 if not prosody.hosts[host] then | 1662 if not prosody.hosts[host] then |
| 1603 return nil, "No such host: "..host; | 1663 return nil, "No such host: "..host; |
| 1604 elseif not um.user_exists(username, host) then | 1664 elseif not um.user_exists(username, host) then |
| 1610 else | 1670 else |
| 1611 return nil, "Could not disable user: "..err; | 1671 return nil, "Could not disable user: "..err; |
| 1612 end | 1672 end |
| 1613 end | 1673 end |
| 1614 | 1674 |
| 1675 describe_command [[user:enable(jid) - Enable the specified user account, restoring login access]] | |
| 1615 function def_env.user:enable(jid) | 1676 function def_env.user:enable(jid) |
| 1616 local username, host = jid_split(jid); | 1677 local username, host = jid_split(jid); |
| 1617 if not prosody.hosts[host] then | 1678 if not prosody.hosts[host] then |
| 1618 return nil, "No such host: "..host; | 1679 return nil, "No such host: "..host; |
| 1619 elseif not um.user_exists(username, host) then | 1680 elseif not um.user_exists(username, host) then |
| 1625 else | 1686 else |
| 1626 return nil, "Could not enable user: "..err; | 1687 return nil, "Could not enable user: "..err; |
| 1627 end | 1688 end |
| 1628 end | 1689 end |
| 1629 | 1690 |
| 1691 describe_command [[user:delete(jid) - Permanently remove the specified user account]] | |
| 1630 function def_env.user:delete(jid) | 1692 function def_env.user:delete(jid) |
| 1631 local username, host = jid_split(jid); | 1693 local username, host = jid_split(jid); |
| 1632 if not prosody.hosts[host] then | 1694 if not prosody.hosts[host] then |
| 1633 return nil, "No such host: "..host; | 1695 return nil, "No such host: "..host; |
| 1634 elseif not um.user_exists(username, host) then | 1696 elseif not um.user_exists(username, host) then |
| 1640 else | 1702 else |
| 1641 return nil, "Could not delete user: "..err; | 1703 return nil, "Could not delete user: "..err; |
| 1642 end | 1704 end |
| 1643 end | 1705 end |
| 1644 | 1706 |
| 1707 describe_command [[user:password(jid, password) - Set the password for the specified user account]] | |
| 1645 function def_env.user:password(jid, password) | 1708 function def_env.user:password(jid, password) |
| 1646 local username, host = jid_split(jid); | 1709 local username, host = jid_split(jid); |
| 1647 if not prosody.hosts[host] then | 1710 if not prosody.hosts[host] then |
| 1648 return nil, "No such host: "..host; | 1711 return nil, "No such host: "..host; |
| 1649 elseif not um.user_exists(username, host) then | 1712 elseif not um.user_exists(username, host) then |
| 1655 else | 1718 else |
| 1656 return nil, "Could not change password for user: "..err; | 1719 return nil, "Could not change password for user: "..err; |
| 1657 end | 1720 end |
| 1658 end | 1721 end |
| 1659 | 1722 |
| 1723 describe_command [[user:roles(jid, host) - Show current roles for an user]] | |
| 1660 function def_env.user:role(jid, host) | 1724 function def_env.user:role(jid, host) |
| 1661 local print = self.session.print; | 1725 local print = self.session.print; |
| 1662 local username, userhost = jid_split(jid); | 1726 local username, userhost = jid_split(jid); |
| 1663 if host == nil then host = userhost; end | 1727 if host == nil then host = userhost; end |
| 1664 if not prosody.hosts[host] then | 1728 if not prosody.hosts[host] then |
| 1680 | 1744 |
| 1681 return true, count == 1 and "1 role" or count.." roles"; | 1745 return true, count == 1 and "1 role" or count.." roles"; |
| 1682 end | 1746 end |
| 1683 def_env.user.roles = def_env.user.role; | 1747 def_env.user.roles = def_env.user.role; |
| 1684 | 1748 |
| 1749 describe_command [[user:setrole(jid, host, role) - Set primary role of a user (see 'help roles')]] | |
| 1685 -- user:setrole("someone@example.com", "example.com", "prosody:admin") | 1750 -- user:setrole("someone@example.com", "example.com", "prosody:admin") |
| 1686 -- user:setrole("someone@example.com", "prosody:admin") | 1751 -- user:setrole("someone@example.com", "prosody:admin") |
| 1687 function def_env.user:setrole(jid, host, new_role) | 1752 function def_env.user:setrole(jid, host, new_role) |
| 1688 local username, userhost = jid_split(jid); | 1753 local username, userhost = jid_split(jid); |
| 1689 if new_role == nil then host, new_role = userhost, host; end | 1754 if new_role == nil then host, new_role = userhost, host; end |
| 1693 return nil, "No such user"; | 1758 return nil, "No such user"; |
| 1694 end | 1759 end |
| 1695 return um.set_user_role(username, host, new_role); | 1760 return um.set_user_role(username, host, new_role); |
| 1696 end | 1761 end |
| 1697 | 1762 |
| 1763 describe_command [[user:addrole(jid, host, role) - Add a secondary role to a user]] | |
| 1698 function def_env.user:addrole(jid, host, new_role) | 1764 function def_env.user:addrole(jid, host, new_role) |
| 1699 local username, userhost = jid_split(jid); | 1765 local username, userhost = jid_split(jid); |
| 1700 if new_role == nil then host, new_role = userhost, host; end | 1766 if new_role == nil then host, new_role = userhost, host; end |
| 1701 if not prosody.hosts[host] then | 1767 if not prosody.hosts[host] then |
| 1702 return nil, "No such host: "..host; | 1768 return nil, "No such host: "..host; |
| 1704 return nil, "No such user"; | 1770 return nil, "No such user"; |
| 1705 end | 1771 end |
| 1706 return um.add_user_secondary_role(username, host, new_role); | 1772 return um.add_user_secondary_role(username, host, new_role); |
| 1707 end | 1773 end |
| 1708 | 1774 |
| 1775 describe_command [[user:delrole(jid, host, role) - Remove a secondary role from a user]] | |
| 1709 function def_env.user:delrole(jid, host, role_name) | 1776 function def_env.user:delrole(jid, host, role_name) |
| 1710 local username, userhost = jid_split(jid); | 1777 local username, userhost = jid_split(jid); |
| 1711 if role_name == nil then host, role_name = userhost, host; end | 1778 if role_name == nil then host, role_name = userhost, host; end |
| 1712 if not prosody.hosts[host] then | 1779 if not prosody.hosts[host] then |
| 1713 return nil, "No such host: "..host; | 1780 return nil, "No such host: "..host; |
| 1715 return nil, "No such user"; | 1782 return nil, "No such user"; |
| 1716 end | 1783 end |
| 1717 return um.remove_user_secondary_role(username, host, role_name); | 1784 return um.remove_user_secondary_role(username, host, role_name); |
| 1718 end | 1785 end |
| 1719 | 1786 |
| 1787 describe_command [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]] | |
| 1720 -- TODO switch to table view, include roles | 1788 -- TODO switch to table view, include roles |
| 1721 function def_env.user:list(host, pat) | 1789 function def_env.user:list(host, pat) |
| 1722 if not host then | 1790 if not host then |
| 1723 return nil, "No host given"; | 1791 return nil, "No host given"; |
| 1724 elseif not prosody.hosts[host] then | 1792 elseif not prosody.hosts[host] then |
| 1734 total = total + 1; | 1802 total = total + 1; |
| 1735 end | 1803 end |
| 1736 return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; | 1804 return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; |
| 1737 end | 1805 end |
| 1738 | 1806 |
| 1739 def_env.xmpp = {}; | 1807 def_env.xmpp = new_section("Commands for sending XMPP stanzas");; |
| 1740 | 1808 |
| 1809 describe_command [[xmpp:ping(localhost, remotehost) - Sends a ping to a remote XMPP server and reports the response]] | |
| 1741 local new_id = require "prosody.util.id".medium; | 1810 local new_id = require "prosody.util.id".medium; |
| 1742 function def_env.xmpp:ping(localhost, remotehost, timeout) | 1811 function def_env.xmpp:ping(localhost, remotehost, timeout) |
| 1743 localhost = select(2, jid_split(localhost)); | 1812 localhost = select(2, jid_split(localhost)); |
| 1744 remotehost = select(2, jid_split(remotehost)); | 1813 remotehost = select(2, jid_split(remotehost)); |
| 1745 if not localhost then | 1814 if not localhost then |
| 1787 end):next(function(pong) | 1856 end):next(function(pong) |
| 1788 return ("pong from %s on %s in %gs"):format(pong.stanza.attr.from, pong.origin.id, time.now() - time_start); | 1857 return ("pong from %s on %s in %gs"):format(pong.stanza.attr.from, pong.origin.id, time.now() - time_start); |
| 1789 end); | 1858 end); |
| 1790 end | 1859 end |
| 1791 | 1860 |
| 1792 def_env.dns = {}; | 1861 def_env.dns = new_section("Commands to manage and inspect the internal DNS resolver"); |
| 1793 local adns = require"prosody.net.adns"; | 1862 local adns = require"prosody.net.adns"; |
| 1794 | 1863 |
| 1795 local function get_resolver(session) | 1864 local function get_resolver(session) |
| 1796 local resolver = session.dns_resolver; | 1865 local resolver = session.dns_resolver; |
| 1797 if not resolver then | 1866 if not resolver then |
| 1799 session.dns_resolver = resolver; | 1868 session.dns_resolver = resolver; |
| 1800 end | 1869 end |
| 1801 return resolver; | 1870 return resolver; |
| 1802 end | 1871 end |
| 1803 | 1872 |
| 1873 describe_command [[dns:lookup(name, type, class) - Do a DNS lookup]] | |
| 1804 function def_env.dns:lookup(name, typ, class) | 1874 function def_env.dns:lookup(name, typ, class) |
| 1805 local resolver = get_resolver(self.session); | 1875 local resolver = get_resolver(self.session); |
| 1806 return resolver:lookup_promise(name, typ, class) | 1876 return resolver:lookup_promise(name, typ, class) |
| 1807 end | 1877 end |
| 1808 | 1878 |
| 1879 describe_command [[dns:addnameserver(nameserver) - Add a nameserver to the list]] | |
| 1809 function def_env.dns:addnameserver(...) | 1880 function def_env.dns:addnameserver(...) |
| 1810 local resolver = get_resolver(self.session); | 1881 local resolver = get_resolver(self.session); |
| 1811 resolver._resolver:addnameserver(...) | 1882 resolver._resolver:addnameserver(...) |
| 1812 return true | 1883 return true |
| 1813 end | 1884 end |
| 1814 | 1885 |
| 1886 describe_command [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]] | |
| 1815 function def_env.dns:setnameserver(...) | 1887 function def_env.dns:setnameserver(...) |
| 1816 local resolver = get_resolver(self.session); | 1888 local resolver = get_resolver(self.session); |
| 1817 resolver._resolver:setnameserver(...) | 1889 resolver._resolver:setnameserver(...) |
| 1818 return true | 1890 return true |
| 1819 end | 1891 end |
| 1820 | 1892 |
| 1893 describe_command [[dns:purge() - Clear the DNS cache]] | |
| 1821 function def_env.dns:purge() | 1894 function def_env.dns:purge() |
| 1822 local resolver = get_resolver(self.session); | 1895 local resolver = get_resolver(self.session); |
| 1823 resolver._resolver:purge() | 1896 resolver._resolver:purge() |
| 1824 return true | 1897 return true |
| 1825 end | 1898 end |
| 1826 | 1899 |
| 1900 describe_command [[dns:cache() - Show cached records]] | |
| 1827 function def_env.dns:cache() | 1901 function def_env.dns:cache() |
| 1828 local resolver = get_resolver(self.session); | 1902 local resolver = get_resolver(self.session); |
| 1829 return true, "Cache:\n"..tostring(resolver._resolver.cache) | 1903 return true, "Cache:\n"..tostring(resolver._resolver.cache) |
| 1830 end | 1904 end |
| 1831 | 1905 |
| 1832 def_env.http = {}; | 1906 def_env.http = new_section("Commands to inspect HTTP services"); |
| 1833 | 1907 |
| 1908 describe_command [[http:list(hosts) - Show HTTP endpoints]] | |
| 1834 function def_env.http:list(hosts) | 1909 function def_env.http:list(hosts) |
| 1835 local print = self.session.print; | 1910 local print = self.session.print; |
| 1836 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); | 1911 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); |
| 1837 local output_simple = format_table({ | 1912 local output_simple = format_table({ |
| 1838 { title = "Module"; width = "1p" }; | 1913 { title = "Module"; width = "1p" }; |
| 1873 print("HTTP requests to unknown hosts will be handled by "..default_host); | 1948 print("HTTP requests to unknown hosts will be handled by "..default_host); |
| 1874 end | 1949 end |
| 1875 return true; | 1950 return true; |
| 1876 end | 1951 end |
| 1877 | 1952 |
| 1878 def_env.watch = {}; | 1953 def_env.watch = new_section("Commands for watching live logs from the server"); |
| 1879 | 1954 |
| 1955 describe_command [[watch:log() - Follow debug logs]] | |
| 1880 function def_env.watch:log() | 1956 function def_env.watch:log() |
| 1881 local writing = false; | 1957 local writing = false; |
| 1882 local sink = logger.add_simple_sink(function (source, level, message) | 1958 local sink = logger.add_simple_sink(function (source, level, message) |
| 1883 if writing then return; end | 1959 if writing then return; end |
| 1884 writing = true; | 1960 writing = true; |
| 1892 if not logger.remove_sink(sink) then | 1968 if not logger.remove_sink(sink) then |
| 1893 module:log("warn", "Unable to remove watch:log() sink"); | 1969 module:log("warn", "Unable to remove watch:log() sink"); |
| 1894 end | 1970 end |
| 1895 end | 1971 end |
| 1896 | 1972 |
| 1973 describe_command [[watch:stanzas(target, filter) - Watch live stanzas matching the specified target and filter]] | |
| 1897 local stanza_watchers = module:require("mod_debug_stanzas/watcher"); | 1974 local stanza_watchers = module:require("mod_debug_stanzas/watcher"); |
| 1898 function def_env.watch:stanzas(target_spec, filter_spec) | 1975 function def_env.watch:stanzas(target_spec, filter_spec) |
| 1899 local function handler(event_type, stanza, session) | 1976 local function handler(event_type, stanza, session) |
| 1900 if stanza then | 1977 if stanza then |
| 1901 if event_type == "sent" then | 1978 if event_type == "sent" then |
| 1927 end | 2004 end |
| 1928 | 2005 |
| 1929 stanza_watchers.remove(handler); | 2006 stanza_watchers.remove(handler); |
| 1930 end | 2007 end |
| 1931 | 2008 |
| 1932 def_env.debug = {}; | 2009 def_env.debug = new_section("Commands for debugging the server"); |
| 1933 | 2010 |
| 2011 describe_command [[debug:logevents(host) - Enable logging of fired events on host]] | |
| 1934 function def_env.debug:logevents(host) | 2012 function def_env.debug:logevents(host) |
| 1935 if host == "*" then | 2013 if host == "*" then |
| 1936 helpers.log_events(prosody.events); | 2014 helpers.log_events(prosody.events); |
| 1937 elseif host == "http" then | 2015 elseif host == "http" then |
| 1938 helpers.log_events(require "prosody.net.http.server"._events); | 2016 helpers.log_events(require "prosody.net.http.server"._events); |
| 1941 helpers.log_host_events(host); | 2019 helpers.log_host_events(host); |
| 1942 end | 2020 end |
| 1943 return true; | 2021 return true; |
| 1944 end | 2022 end |
| 1945 | 2023 |
| 2024 describe_command [[debug:events(host, event) - Show registered event handlers]] | |
| 1946 function def_env.debug:events(host, event) | 2025 function def_env.debug:events(host, event) |
| 1947 local events_obj; | 2026 local events_obj; |
| 1948 if host and host ~= "*" then | 2027 if host and host ~= "*" then |
| 1949 if host == "http" then | 2028 if host == "http" then |
| 1950 events_obj = require "prosody.net.http.server"._events; | 2029 events_obj = require "prosody.net.http.server"._events; |
| 1957 events_obj = prosody.events; | 2036 events_obj = prosody.events; |
| 1958 end | 2037 end |
| 1959 return true, helpers.show_events(events_obj, event); | 2038 return true, helpers.show_events(events_obj, event); |
| 1960 end | 2039 end |
| 1961 | 2040 |
| 2041 describe_command [[debug:timers() - Show information about scheduled timers]] | |
| 1962 function def_env.debug:timers() | 2042 function def_env.debug:timers() |
| 1963 local print = self.session.print; | 2043 local print = self.session.print; |
| 1964 local add_task = require"prosody.util.timer".add_task; | 2044 local add_task = require"prosody.util.timer".add_task; |
| 1965 local h, params = add_task.h, add_task.params; | 2045 local h, params = add_task.h, add_task.params; |
| 1966 local function normalize_time(t) | 2046 local function normalize_time(t) |
| 2013 end | 2093 end |
| 2014 end | 2094 end |
| 2015 return true; | 2095 return true; |
| 2016 end | 2096 end |
| 2017 | 2097 |
| 2098 describe_command [[debug:async() - Show information about pending asynchronous tasks]] | |
| 2018 function def_env.debug:async(runner_id) | 2099 function def_env.debug:async(runner_id) |
| 2019 local print = self.session.print; | 2100 local print = self.session.print; |
| 2020 local time_now = time.now(); | 2101 local time_now = time.now(); |
| 2021 | 2102 |
| 2022 if runner_id then | 2103 if runner_id then |
| 2078 end | 2159 end |
| 2079 | 2160 |
| 2080 -- COMPAT: debug:timers() was timer:info() for some time in trunk | 2161 -- COMPAT: debug:timers() was timer:info() for some time in trunk |
| 2081 def_env.timer = { info = def_env.debug.timers }; | 2162 def_env.timer = { info = def_env.debug.timers }; |
| 2082 | 2163 |
| 2083 def_env.stats = {}; | 2164 def_env.stats = new_section("Commands to show internal statistics"); |
| 2084 | 2165 |
| 2085 local short_units = { | 2166 local short_units = { |
| 2086 seconds = "s", | 2167 seconds = "s", |
| 2087 bytes = "B", | 2168 bytes = "B", |
| 2088 }; | 2169 }; |
| 2317 -- Otherwise we get strange stuff like average cpu usage decreasing until | 2398 -- Otherwise we get strange stuff like average cpu usage decreasing until |
| 2318 -- the next sample and so on. | 2399 -- the next sample and so on. |
| 2319 return setmetatable({ session = self.session, stats = true, now = time.now() }, stats_mt); | 2400 return setmetatable({ session = self.session, stats = true, now = time.now() }, stats_mt); |
| 2320 end | 2401 end |
| 2321 | 2402 |
| 2403 describe_command [[stats:show(pattern) - Show internal statistics, optionally filtering by name with a pattern. Append :cfgraph() or :histogram() for graphs]] | |
| 2322 function def_env.stats:show(name_filter) | 2404 function def_env.stats:show(name_filter) |
| 2323 local statsman = require "prosody.core.statsmanager" | 2405 local statsman = require "prosody.core.statsmanager" |
| 2324 local collect = statsman.collect | 2406 local collect = statsman.collect |
| 2325 if collect then | 2407 if collect then |
| 2326 -- force collection if in manual mode | 2408 -- force collection if in manual mode |