mirror of
https://github.com/bjc/prosody.git
synced 2025-04-03 21:27:38 +03:00
This allow a shell-command to provide a 'flags' field, which will automatically cause the parameters to be fed through argparse. The rationale is to make it easier for more complex commands to be invoked from the command line (`prosodyctl shell foo bar ...`). Until now they were limited to accepting a list of strings, and any complex argument processing was non-standard and awkward to implement.
181 lines
5.3 KiB
Lua
181 lines
5.3 KiB
Lua
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)
|
|
client.send(st.stanza("repl-input", { width = tostring(term_width()) }):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]);
|
|
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 }):text(password));
|
|
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;
|
|
};
|