IMPORTANT: due to a drive failure, as of 13-Mar-2021, the Mercurial repository had to be re-mirrored, which changed every commit SHA. The old SHAs and trees are backed up in the vault branches. Please migrate to the new branches as soon as you can.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
prosody/util/prosodyctl/shell.lua

186 lines
5.4 KiB

local config = require "prosody.core.configmanager";
local human_io = require "prosody.util.human.io";
local server = require "prosody.net.server";
local st = require "prosody.util.stanza";
local path = require "prosody.util.paths";
local parse_args = require "prosody.util.argparse".parse;
local tc = require "prosody.util.termcolours";
local isatty = require "prosody.util.pposix".isatty;
local term_width = require"prosody.util.human.io".term_width;
local have_readline, readline = pcall(require, "readline");
local adminstream = require "prosody.util.adminstream";
if have_readline then
readline.set_readline_name("prosody");
readline.set_options({
histfile = path.join(prosody.paths.data, ".shell_history");
ignoredups = true;
});
end
local function read_line(prompt_string)
if have_readline then
return readline.readline(prompt_string);
else
io.write(prompt_string);
return io.read("*line");
end
end
local function send_line(client, line, interactive)
client.send(st.stanza("repl-input", { width = tostring(term_width()), repl = interactive == false and "0" or "1" }):text(line));
end
local function repl(client)
local line = read_line(client.prompt_string or "prosody> ");
if not line or line == "quit" or line == "exit" or line == "bye" then
if not line then
print("");
end
if have_readline then
readline.save_history();
end
os.exit(0, true);
end
send_line(client, line);
end
local function printbanner()
local banner = config.get("*", "console_banner");
if banner then return print(banner); end
print([[
____ \ / _
| _ \ _ __ ___ ___ _-_ __| |_ _
| |_) | '__/ _ \/ __|/ _ \ / _` | | | |
| __/| | | (_) \__ \ |_| | (_| | |_| |
|_| |_| \___/|___/\___/ \__,_|\__, |
A study in simplicity |___/
]]);
print("Welcome to the Prosody administration console. For a list of commands, type: help");
print("You may find more help on using this console in our online documentation at ");
print("https://prosody.im/doc/console\n");
end
local function check()
local lfs = require "lfs";
local socket_path = path.resolve_relative_path(prosody.paths.data, config.get("*", "admin_socket") or "prosody.sock");
local state = lfs.attributes(socket_path, "mode");
return state == "socket";
end
local function start(arg) --luacheck: ignore 212/arg
local client = adminstream.client();
local opts, err, where = parse_args(arg);
local ttyout = isatty(io.stdout);
if not opts then
if err == "param-not-found" then
print("Unknown command-line option: "..tostring(where));
elseif err == "missing-value" then
print("Expected a value to follow command-line option: "..where);
end
os.exit(1);
end
if arg[1] then
if arg[2] then
arg[1] = ("{"..string.rep("%q", #arg, ", ").."}"):format(table.unpack(arg, 1, #arg));
end
client.events.add_handler("connected", function()
send_line(client, arg[1], false);
return true;
end, 1);
local errors = 0; -- TODO This is weird, but works for now.
client.events.add_handler("received", function(stanza)
if stanza.name == "repl-output" or stanza.name == "repl-result" then
local dest = io.stdout;
if stanza.attr.type == "error" then
errors = errors + 1;
dest = io.stderr;
end
if stanza.attr.eol == "0" then
dest:write(stanza:get_text());
else
dest:write(stanza:get_text(), "\n");
end
end
if stanza.name == "repl-result" then
os.exit(errors);
end
return true;
end, 1);
end
client.events.add_handler("connected", function ()
if not opts.quiet then
printbanner();
end
repl(client);
end);
client.events.add_handler("disconnected", function ()
print("--- session closed ---");
os.exit(0, true);
end);
client.events.add_handler("received", function (stanza)
if stanza.name ~= "repl-request-input" then
return;
end
if stanza.attr.type == "password" then
local password = human_io.read_password();
client.send(st.stanza("repl-requested-input", {
type = stanza.attr.type;
id = stanza.attr.id;
status = password and "submit" or "cancel";
}):text(password or ""));
else
io.stderr:write("Internal error - unexpected input request type "..tostring(stanza.attr.type).."\n");
os.exit(1);
end
return true;
end, 2);
client.events.add_handler("received", function (stanza)
if stanza.name == "repl-output" or stanza.name == "repl-result" then
local result_prefix = stanza.attr.type == "error" and "!" or "|";
local out = result_prefix.." "..stanza:get_text();
if ttyout and stanza.attr.type == "error" then
out = tc.getstring(tc.getstyle("red"), out);
end
print(out);
end
if stanza.name == "repl-result" then
repl(client);
end
end);
client.prompt_string = config.get("*", "admin_shell_prompt");
local socket_path = path.resolve_relative_path(prosody.paths.data, opts.socket or config.get("*", "admin_socket") or "prosody.sock");
local conn = adminstream.connection(socket_path, client.listeners);
local ok, err = conn:connect();
if not ok then
if err == "no unix socket support" then
print("** LuaSocket unix socket support not available or incompatible, ensure your");
print("** version is up to date.");
else
print("** Unable to connect to server - is it running? Is mod_admin_shell enabled?");
print("** Connection error: "..err);
end
os.exit(1);
end
server.loop();
end
return {
shell = start;
available = check;
};