EXPERIMENTAL
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 20s

This commit is contained in:
Your Name 2025-05-10 18:19:02 +12:00
parent f9dca5fea1
commit dbcef96bc2
4 changed files with 378 additions and 389 deletions

View File

@ -134,11 +134,13 @@ std::string server_env_manager::get_variable(const std::string& name) const {
bool server_env_manager::check_remote_dir_exists(const std::string &dir_path) const
{
return 0==runner::execute_cmd("test",{"-d", quote(dir_path)}, {}, {}, true, false, get_SSH_INFO());
runner::runner_ssh test_runner(get_SSH_INFO(),"test",{"-d", quote(dir_path)});
return test_runner.execute();
}
bool server_env_manager::check_remote_file_exists(const std::string& file_path) const {
return 0==runner::execute_cmd("test",{"-f",quote(file_path)}, {}, {}, true, false, get_SSH_INFO());
runner::runner_ssh test_runner(get_SSH_INFO(),"test",{"-f",quote(file_path)});
return test_runner.execute();
}
bool server_env_manager::check_remote_items_exist(const std::vector<std::string> &file_paths) const
@ -149,8 +151,8 @@ bool server_env_manager::check_remote_items_exist(const std::vector<std::string>
file_paths_str += quote(file_path) + " ";
}
// check if all items in the vector exist on the remote server, in a single command.
return 0==runner::execute_cmd("bash",{"-c","for item in " + file_paths_str + "; do test -f $item; done"}, {}, {}, true, false, get_SSH_INFO());
runner::runner_ssh test_runner(get_SSH_INFO(),"bash",{"-c","for item in " + file_paths_str + "; do test -f $item; done"});
return test_runner.execute();
}
bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars, std::string * output) const
@ -177,7 +179,13 @@ bool server_env_manager::run_remote_template_command(const std::string &service_
ASSERT(!output || !silent); // if output is captured, silent must be false
ASSERT(!interactive || !silent); // if command is ssh, silent must be false
return 0==runner::execute_cmd("bash",{"-c", quote(script_path) + argstr}, working_dir, env_vars, silent, interactive, get_SSH_INFO(), output);
if (interactive) {
runner::runner_ssh_interactive bash_runner(get_SSH_INFO(),"bash",{"-c", quote(script_path) + argstr}, working_dir, env_vars);
return bash_runner.execute();
} else {
runner::runner_ssh bash_runner(get_SSH_INFO(),"bash",{"-c", quote(script_path) + argstr}, working_dir, env_vars, silent);
return bash_runner.execute();
}
}
// base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh"

View File

@ -52,18 +52,23 @@ bool service_runner::install(bool silent) {
if (!tinfo.is_set())
return false;
// Create service directory
if (0!=runner::execute_cmd("mkdir",{"-p",remotepath::service(mServer, mService)},"",{},true,false,mServerEnv.get_SSH_INFO()))
{
std::cerr << "Failed to create service directory " << remotepath::service(mServer, mService) << std::endl;
return false;
{ // Create service directory
runner::runner_ssh mkdir_runner(mServerEnv.get_SSH_INFO(),"mkdir",{"-p",remotepath::service(mServer, mService)});
if (!mkdir_runner.execute())
{
std::cerr << "Failed to create service directory " << remotepath::service(mServer, mService) << std::endl;
return false;
}
}
// Check if rsync is installed on remote host
if (0!=runner::execute_cmd("which",{"rsync"},"",{},true,false,mServerEnv.get_SSH_INFO()))
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
{ // Check if rsync is installed on remote host
runner::runner_ssh which_runner(mServerEnv.get_SSH_INFO(),"which",{"rsync"});
if (!which_runner.execute())
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
}
}
// make sure all shell files are executable
@ -125,8 +130,11 @@ bool service_runner::uninstall(bool silent) {
}
// 4. Remove the service directory from the server
if (0!=runner::execute_cmd("rm -rf " + quote(remotepath::service(mServer, mService)), {}, "", {}, true, false, mServerEnv.get_SSH_INFO()))
std::cerr << "Failed to remove remote service directory at " << remotepath::service(mServer, mService) << std::endl;
{
runner::runner_ssh rm_runner(mServerEnv.get_SSH_INFO(),"rm",{"-rf",remotepath::service(mServer, mService)});
if (!rm_runner.execute())
std::cerr << "Failed to remove remote service directory at " << remotepath::service(mServer, mService) << std::endl;
}
std::cout << "Service " << mService << " successfully uninstalled from " << mServer << std::endl;
return true;
@ -170,8 +178,11 @@ bool service_runner::fullnuke()
return false;
}
if (0!=runner::execute_cmd("rm -rf " + quote(local_service_path), {}, "", {}, true, false, mServerEnv.get_SSH_INFO()))
std::cerr << "Failed to remove local service directory at " << local_service_path << std::endl;
{
runner::runner_local rm_runner("rm",{"-rf",quote(local_service_path)});
if (!rm_runner.execute())
std::cerr << "Failed to remove local service directory at " << local_service_path << std::endl;
}
std::cout << "Service " << mService << " successfully fully nuked from " << mServer << std::endl;
return true;
@ -265,7 +276,6 @@ std::map<std::string, ServiceStatus> service_runner::get_all_services_status(std
{
std::map<std::string, ServiceStatus> status;
std::string command = "_allservicesstatus";
std::string service_name = "dropshell-agent";
@ -275,12 +285,15 @@ std::map<std::string, ServiceStatus> service_runner::get_all_services_status(std
return status;
}
std::string cmd_path = remotepath::service_template(server_name,service_name) + "/shared/";
std::string cmd_path = remotepath::service_template(server_name,service_name) + "/shared/_allservicesstatus.sh";
std::string output;
if (0!=runner::execute_cmd("bash",{cmd_path+command+".sh"}, cmd_path, {}, true, false, env.get_SSH_INFO(), &output))
{
std::cerr << "Error: Failed to run command script at " << cmd_path << std::endl;
return status;
runner::runner_ssh_capture bash_runner(env.get_SSH_INFO(),output,cmd_path);
if (!bash_runner.execute())
{
std::cerr << "Error: Failed to run command script at " << cmd_path << std::endl;
return status;
}
}
std::stringstream ss(output);
@ -390,7 +403,8 @@ bool service_runner::interactive_ssh(const std::string & server_name, const std:
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
return false;
}
return 0==runner::execute_cmd("", {}, "", {}, false, true, env.get_SSH_INFO());
runner::runner_ssh_interactive ssh_runner(env.get_SSH_INFO(),command);
return ssh_runner.execute();
}
void service_runner::edit_server(const std::string &server_name)
@ -438,7 +452,8 @@ bool service_runner::edit_file(const std::string &file_path)
}
std::cout << "Editing file: " << file_path << std::endl;
return 0==runner::execute_cmd(editor_cmd, {file_path}, "", {}, false, true, nullptr);
runner::runner_local_interactive editor_runner(editor_cmd,{file_path});
return editor_runner.execute();
}
bool service_runner::interactive_ssh_service()
@ -544,7 +559,8 @@ bool service_runner::restore(std::string backup_file, bool silent)
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// Copy backup file from local to server
if (0!=runner::execute_cmd("scp", {quote(local_backup_file_path), mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_backup_file_path)}, "", {}, false, false, mServerEnv.get_SSH_INFO()))
runner::runner_local scp_runner("scp",{quote(local_backup_file_path), mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_backup_file_path)});
if (!scp_runner.execute())
{
std::cerr << "Failed to copy backup file from server" << std::endl;
return false;
@ -621,11 +637,14 @@ bool service_runner::backup(bool silent) {
// Create backups directory on server if it doesn't exist
std::string remote_backups_dir = remotepath::backups(mServer);
if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
if (0!=runner::execute_cmd("mkdir", {"-p",quote(remote_backups_dir)}, "", {}, true, false, mServerEnv.get_SSH_INFO()))
if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
{
std::cerr << "Failed to create backups directory on server" << std::endl;
return false;
runner::runner_ssh mkdir_runner(mServerEnv.get_SSH_INFO(),"mkdir",{"-p",quote(remote_backups_dir)});
if (!mkdir_runner.execute())
{
std::cerr << "Failed to create backups directory on server" << std::endl;
return false;
}
}
// Create backups directory locally if it doesn't exist
@ -664,7 +683,8 @@ bool service_runner::backup(bool silent) {
}
// Copy backup file from server to local
if (0!=runner::execute_cmd("scp", {quote(remote_backup_file_path), quote(local_backup_file_path)}, "", {}, silent, false, mServerEnv.get_SSH_INFO()))
runner::runner_local scp_runner("scp",{"-P", mServerEnv.get_SSH_PORT(), mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_backup_file_path), quote(local_backup_file_path)});
if (!scp_runner.execute())
{
std::cerr << "Failed to copy backup file from server" << std::endl;
return false;
@ -681,15 +701,19 @@ bool service_runner::backup(bool silent) {
cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env)
{
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10);
if (0!=runner::execute_cmd("mkdir", {"-p",quote(p)}, "", {}, true, false, server_env.get_SSH_INFO()))
std::cerr << "Failed to create temp directory on server" << std::endl;
else
mPath = p;
{
runner::runner_ssh mkdir_runner(server_env.get_SSH_INFO(),"mkdir",{"-p",quote(p)});
if (!mkdir_runner.execute())
std::cerr << "Failed to create temp directory on server" << std::endl;
else
mPath = p;
}
}
cRemoteTempFolder::~cRemoteTempFolder()
{
if (0!=runner::execute_cmd("rm", {"-rf",quote(mPath)}, "", {}, true, false, mServerEnv.get_SSH_INFO()))
runner::runner_ssh rm_runner(mServerEnv.get_SSH_INFO(),"rm",{"-rf",quote(mPath)});
if (!rm_runner.execute())
std::cerr << "Failed to remove temp directory on server" << std::endl;
}
@ -748,11 +772,10 @@ std::string service_runner::get_latest_backup_file(const std::string& server, co
bool service_runner::rsync_copy(const std::string& local_path, const std::string& remote_path, bool silent) {
std::cout << "Copying: [LOCAL] " << local_path << std::endl << std::string(8,' ')<<"[REMOTE] " << remote_path << std::endl;
if (0 != runner::execute_cmd(
"rsync",
{"--delete","--mkpath","-zrpc","-e",quote("ssh -p " + mServerEnv.get_SSH_PORT()),local_path,
mServerEnv.get_SSH_USER()+"@"+mServerEnv.get_SSH_HOST()+":"+remote_path},
"", {}, true, false)) {
runner::runner_local rsync_runner("rsync", {"--delete","--mkpath","-zrpc","-e",quote("ssh -p " + mServerEnv.get_SSH_PORT()),local_path,
mServerEnv.get_SSH_USER()+"@"+mServerEnv.get_SSH_HOST()+":"+remote_path}, "", {}, true);
if (!rsync_runner.execute()) {
std::cerr << "Failed to copy files using rsync" << std::endl;
return false;
}

View File

@ -77,364 +77,281 @@ ssh_session ssh_connect_and_auth(const sSSHInfo* sshinfo, const std::map<std::st
return session;
}
std::string ssh_build_remote_command(const std::string& command, const std::vector<std::string>& args, const std::string& working_dir, const std::map<std::string, std::string>& env) {
std::ostringstream remote_cmd;
for (const auto& kv : env) {
if (kv.first == "SSHPASS") continue;
remote_cmd << kv.first << "='" << kv.second << "' ";
}
if (!working_dir.empty()) {
remote_cmd << "cd '" << working_dir << "' && ";
}
remote_cmd << command;
for (const auto& arg : args) {
remote_cmd << " " << arg;
}
return remote_cmd.str();
}
} // namespace runner
// // Utility function to escape special shell characters
// std::string escape_shell_arg(const std::string& arg) {
// std::ostringstream escaped;
// escaped << '"';
// for (char c : arg) {
// if (c == '"' || c == '\\' || c == '$' || c == '`') {
// escaped << '\\';
// }
// escaped << c;
// }
// escaped << '"';
// return escaped.str();
// }
// // For non-interactive SSH, just build the command with args
// std::string ssh_build_command_only(const std::string& command, const std::vector<std::string>& args) {
// std::ostringstream remote_cmd;
// remote_cmd << command;
// for (const auto& arg : args) {
// remote_cmd << " " << arg;
// }
// return remote_cmd.str();
// }
int ssh_interactive_shell_session(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, const std::string& command, std::string* output) {
// Request a PTY with xterm-256color type for color support
// First try using default terminal settings - should work with libssh 0.9.0+
int rc = ssh_channel_request_pty(channel);
if (rc != SSH_OK) {
if (output) *output = std::string("Failed to request pty: ") + ssh_get_error(session);
return -1;
}
// Try to explicitly tell the server we want colors through additional means
// 1. Set TERM environment variable
rc = ssh_channel_request_env(channel, "TERM", "xterm-256color");
// Ignore errors - this is optional
// 2. Request a shell
rc = ssh_channel_request_shell(channel);
if (rc != SSH_OK) {
if (output) *output = std::string("Failed to request shell: ") + ssh_get_error(session);
return -1;
}
struct termios orig_termios, raw_termios;
tcgetattr(STDIN_FILENO, &orig_termios);
raw_termios = orig_termios;
cfmakeraw(&raw_termios);
tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios);
if (!command.empty()) {
ssh_channel_write(channel, remote_cmd_str.c_str(), remote_cmd_str.size());
ssh_channel_write(channel, "\n", 1);
} else {
// Initialize bash with color support if no specific command
std::string init_cmd = "export TERM=xterm-256color && if [ -f ~/.bashrc ]; then source ~/.bashrc; fi";
ssh_channel_write(channel, init_cmd.c_str(), init_cmd.size());
ssh_channel_write(channel, "\n", 1);
}
int maxfd = STDIN_FILENO > STDOUT_FILENO ? STDIN_FILENO : STDOUT_FILENO;
maxfd = maxfd > ssh_get_fd(session) ? maxfd : ssh_get_fd(session);
char buffer[4096];
bool done = false;
while (!done) {
fd_set fds_read;
FD_ZERO(&fds_read);
FD_SET(STDIN_FILENO, &fds_read);
FD_SET(ssh_get_fd(session), &fds_read);
int ret = select(maxfd + 1, &fds_read, nullptr, nullptr, nullptr);
if (ret < 0) break;
if (FD_ISSET(STDIN_FILENO, &fds_read)) {
ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer));
if (n > 0) {
ssh_channel_write(channel, buffer, n);
} else {
ssh_channel_send_eof(channel);
done = true;
}
}
if (FD_ISSET(ssh_get_fd(session), &fds_read)) {
int n = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
if (n > 0) {
write(STDOUT_FILENO, buffer, n);
} else if (n == 0) {
done = true;
}
}
if (ssh_channel_is_closed(channel) || ssh_channel_is_eof(channel)) {
done = true;
}
}
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
return 0;
}
int ssh_exec_command(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, bool silent, std::string* output, const std::map<std::string, std::string>& env, const std::string& working_dir) {
// Build complete command with env, working_dir, and the command itself
std::ostringstream cmd_with_env;
// Create a simple, flat command that will work reliably
// Format: env VAR=value bash -c 'cd /path && command args'
// Start with env variables
if (!env.empty()) {
cmd_with_env << "env ";
for (const auto& kv : env) {
if (kv.first == "SSHPASS") continue;
cmd_with_env << kv.first << "=\"" << kv.second << "\" ";
}
}
// Use a single bash -c with the entire command inside single quotes
cmd_with_env << "bash -c '";
// Add cd if working directory specified
if (!working_dir.empty()) {
cmd_with_env << "cd " << working_dir << " && ";
}
cmd_with_env << remote_cmd_str;
// Close the single quote
cmd_with_env << "'";
std::string final_cmd = cmd_with_env.str();
std::cout << "Final remote command: " << final_cmd << std::endl;
int rc = ssh_channel_request_exec(channel, final_cmd.c_str());
if (rc != SSH_OK) {
std::string error = std::string("Failed to exec remote command: ") + ssh_get_error(session);
std::cerr << "SSH exec error: " << error << std::endl;
if (output) *output = error;
return -1;
}
if (output) {
std::ostringstream oss;
char buffer[4096];
int nbytes;
// Read from stdout
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
oss.write(buffer, nbytes);
}
if (nbytes < 0) {
std::cerr << "Error reading from stdout" << std::endl;
}
// Read from stderr
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) {
oss.write(buffer, nbytes);
}
if (nbytes < 0) {
std::cerr << "Error reading from stderr" << std::endl;
}
*output = oss.str();
} else if (!silent) {
char buffer[4096];
int nbytes;
// Read from stdout
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
write(1, buffer, nbytes);
}
if (nbytes < 0) {
std::cerr << "Error reading from stdout" << std::endl;
}
// Read from stderr
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) {
write(2, buffer, nbytes);
}
if (nbytes < 0) {
std::cerr << "Error reading from stderr" << std::endl;
}
}
return 0;
}
int local_execute_cmd(
const std::string& command,
const std::vector<std::string>& args,
const std::string& working_dir,
const std::map<std::string, std::string>& env,
bool silent,
bool interactive,
std::string* output
) {
std::cerr << "Local command: " << command << std::endl;
int pipefd[2];
bool use_pipe = output && !interactive;
if (use_pipe && pipe(pipefd) == -1) {
perror("pipe");
std::cerr << "Pipe error: " << strerror(errno) << std::endl;
return -1;
}
// runner_local: non-interactive local command execution
bool runner_local::execute() {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
std::cerr << "Fork error: " << strerror(errno) << std::endl;
return -1;
std::cerr << "Failed to fork" << std::endl;
mReturnCode = -1;
return false;
}
if (pid == 0) {
if (!working_dir.empty()) {
if (chdir(working_dir.c_str()) != 0) {
// Child process
if (!mWorkingDir.empty()) {
if (chdir(mWorkingDir.c_str()) != 0) {
perror("chdir");
std::cerr << "Chdir error: " << strerror(errno) << std::endl;
exit(-1);
exit(127);
}
}
for (const auto& kv : env) {
setenv(kv.first.c_str(), kv.second.c_str(), 1);
}
if (use_pipe) {
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
} else if (silent && !interactive) {
int devnull = open("/dev/null", O_WRONLY);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
close(devnull);
}
if (!interactive) {
setsid();
// Set environment variables
for (const auto& [k, v] : mEnv) {
setenv(k.c_str(), v.c_str(), 1);
}
// Build argv
std::vector<char*> argv;
argv.push_back(const_cast<char*>(command.c_str()));
for (const auto& arg : args) {
argv.push_back(const_cast<char*>(arg.c_str()));
}
argv.push_back(const_cast<char*>(mCommand.c_str()));
for (auto& arg : mArgs) argv.push_back(const_cast<char*>(arg.c_str()));
argv.push_back(nullptr);
std::cerr << "Local command: " << command << std::endl;
for (const auto& arg : args) {
std::cerr << "Local arg: " << arg << std::endl;
}
execvp(command.c_str(), argv.data());
perror("execvp");
exit(-1);
} else {
if (use_pipe) {
close(pipefd[1]);
std::ostringstream oss;
char buf[4096];
ssize_t n;
while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
oss.write(buf, n);
if (mSilent) {
int fd = open("/dev/null", O_WRONLY);
if (fd != -1) {
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
}
close(pipefd[0]);
*output = oss.str();
}
execvp(mCommand.c_str(), argv.data());
perror("execvp");
exit(127);
} else {
int status = 0;
waitpid(pid, &status, 0);
std::cerr << "Waitpid status: " << status << std::endl;
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
mReturnCode = WEXITSTATUS(status);
} else {
return -1;
mReturnCode = -1;
}
return mReturnCode == 0;
}
}
} // anonymous namespace
int execute_cmd(
const std::string& command,
const std::vector<std::string>& args,
const std::string& working_dir,
const std::map<std::string, std::string>& env,
const bool silent,
const bool interactive,
const copySSHPtr& sshinfo,
std::string* output
) {
if (sshinfo.valid()) {
std::string error;
ssh_session session = ssh_connect_and_auth(&sshinfo, env, &error);
if (!session) {
if (output) *output = error;
std::cerr << "SSH connection error: " << error << std::endl;
return -1;
// runner_local_interactive: interactive local command execution
bool runner_local_interactive::execute() {
pid_t pid = fork();
if (pid == -1) {
std::cerr << "Failed to fork" << std::endl;
mReturnCode = -1;
return false;
}
if (pid == 0) {
if (!mWorkingDir.empty()) {
if (chdir(mWorkingDir.c_str()) != 0) {
perror("chdir");
exit(127);
}
}
ssh_channel channel = ssh_channel_new(session);
if (!channel) {
if (output) *output = "Failed to create SSH channel.";
ssh_disconnect(session);
ssh_free(session);
std::cerr << "SSH channel error: " << output->c_str() << std::endl;
return -1;
for (const auto& [k, v] : mEnv) {
setenv(k.c_str(), v.c_str(), 1);
}
int rc = ssh_channel_open_session(channel);
if (rc != SSH_OK) {
if (output) *output = std::string("Failed to open SSH channel: ") + ssh_get_error(session);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
std::cerr << "SSH channel open error: " << output->c_str() << std::endl;
return -1;
}
int ret = 0;
std::string remote_cmd_str = ssh_build_remote_command(command, args, working_dir, {});
if (interactive) {
ret = ssh_interactive_shell_session(session, channel, remote_cmd_str, command, output);
std::vector<char*> argv;
argv.push_back(const_cast<char*>(mCommand.c_str()));
for (auto& arg : mArgs) argv.push_back(const_cast<char*>(arg.c_str()));
argv.push_back(nullptr);
// Attach to terminal (no redirection)
execvp(mCommand.c_str(), argv.data());
perror("execvp");
exit(127);
} else {
int status = 0;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
mReturnCode = WEXITSTATUS(status);
} else {
// For non-interactive, handle working directory in ssh_exec_command
ret = ssh_exec_command(session, channel, remote_cmd_str, silent, output, env, working_dir);
mReturnCode = -1;
}
ssh_channel_send_eof(channel);
return mReturnCode == 0;
}
}
// runner_ssh: non-interactive SSH command execution
bool runner_ssh::execute() {
std::string error;
ssh_session session = ssh_connect_and_auth(&mSSHInfo, mEnv, &error);
if (!session) {
std::cerr << error << std::endl;
mReturnCode = -1;
return false;
}
ssh_channel channel = ssh_channel_new(session);
if (!channel) {
std::cerr << "Failed to create SSH channel." << std::endl;
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
if (ssh_channel_open_session(channel) != SSH_OK) {
std::cerr << "Failed to open SSH channel: " << ssh_get_error(session) << std::endl;
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
std::stringstream cmd;
cmd << mCommand;
for (const auto& arg : mArgs) {
cmd << " '" << arg << "'";
}
int rc = ssh_channel_request_exec(channel, cmd.str().c_str());
if (rc != SSH_OK) {
std::cerr << "SSH exec failed: " << ssh_get_error(session) << std::endl;
ssh_channel_close(channel);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
if (output) trim(output);
std::cerr << "SSH command execution result: " << ret << std::endl;
return ret;
} else {
int ret=local_execute_cmd(command, args, working_dir, env, silent, interactive, output);
if (output) trim(output);
std::cerr << "Local command execution result: " << ret << std::endl;
return ret;
mReturnCode = -1;
return false;
}
char buffer[256];
int nbytes;
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
if (!mSilent) std::cout.write(buffer, nbytes);
}
mReturnCode = ssh_channel_get_exit_status(channel);
ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
return mReturnCode == 0;
}
// runner_ssh_interactive: interactive SSH command execution
bool runner_ssh_interactive::execute() {
std::string error;
ssh_session session = ssh_connect_and_auth(&mSSHInfo, mEnv, &error);
if (!session) {
std::cerr << error << std::endl;
mReturnCode = -1;
return false;
}
ssh_channel channel = ssh_channel_new(session);
if (!channel) {
std::cerr << "Failed to create SSH channel." << std::endl;
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
if (ssh_channel_open_session(channel) != SSH_OK) {
std::cerr << "Failed to open SSH channel: " << ssh_get_error(session) << std::endl;
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
std::stringstream cmd;
cmd << mCommand;
for (const auto& arg : mArgs) {
cmd << " '" << arg << "'";
}
int rc = ssh_channel_request_pty(channel);
if (rc != SSH_OK) {
std::cerr << "Failed to request PTY: " << ssh_get_error(session) << std::endl;
ssh_channel_close(channel);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
rc = ssh_channel_request_shell(channel);
if (rc != SSH_OK) {
std::cerr << "Failed to request shell: " << ssh_get_error(session) << std::endl;
ssh_channel_close(channel);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
// Forward input/output between user and SSH channel
fd_set fds;
char buffer[256];
while (true) {
FD_ZERO(&fds);
FD_SET(0, &fds); // stdin
int ssh_fd = ssh_get_fd(session);
FD_SET(ssh_fd, &fds);
int maxfd = std::max(0, ssh_fd) + 1;
int ret = select(maxfd, &fds, nullptr, nullptr, nullptr);
if (ret < 0) break;
if (FD_ISSET(0, &fds)) {
int n = read(0, buffer, sizeof(buffer));
if (n > 0) ssh_channel_write(channel, buffer, n);
}
if (FD_ISSET(ssh_fd, &fds)) {
int n = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
if (n > 0) write(1, buffer, n);
else if (n == 0) break;
}
if (ssh_channel_is_closed(channel)) break;
}
mReturnCode = ssh_channel_get_exit_status(channel);
ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
return mReturnCode == 0;
}
// runner_ssh_capture: SSH command execution with output capture
bool runner_ssh_capture::execute() {
std::string error;
ssh_session session = ssh_connect_and_auth(&mSSHInfo, mEnv, &error);
if (!session) {
std::cerr << error << std::endl;
mReturnCode = -1;
return false;
}
ssh_channel channel = ssh_channel_new(session);
if (!channel) {
std::cerr << "Failed to create SSH channel." << std::endl;
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
if (ssh_channel_open_session(channel) != SSH_OK) {
std::cerr << "Failed to open SSH channel: " << ssh_get_error(session) << std::endl;
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
std::stringstream cmd;
cmd << mCommand;
for (const auto& arg : mArgs) {
cmd << " '" << arg << "'";
}
int rc = ssh_channel_request_exec(channel, cmd.str().c_str());
if (rc != SSH_OK) {
std::cerr << "SSH exec failed: " << ssh_get_error(session) << std::endl;
ssh_channel_close(channel);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
mReturnCode = -1;
return false;
}
char buffer[256];
int nbytes;
mOutput.clear();
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
mOutput.append(buffer, nbytes);
}
mReturnCode = ssh_channel_get_exit_status(channel);
ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);
ssh_disconnect(session);
ssh_free(session);
return mReturnCode == 0;
}
} // namespace runner

View File

@ -14,26 +14,67 @@ struct sSSHInfo {
std::string port;
};
class copySSHPtr {
class runner {
public:
copySSHPtr(const sSSHInfo* sshinfo) : mSSHInfo(sshinfo ? *sshinfo : sSSHInfo()) {}
copySSHPtr(const sSSHInfo& sshinfo) : mSSHInfo(sshinfo) {}
bool valid() const { return !mSSHInfo.host.empty(); }
const sSSHInfo * operator&() const { return (valid() ? &mSSHInfo : nullptr); }
private:
sSSHInfo mSSHInfo;
};
runner(std::string command, std::vector<std::string> args={}, std::string working_dir="", std::map<std::string, std::string> env={}, bool silent=false) :
mCommand(command),
mArgs(args),
mWorkingDir(working_dir),
mEnv(env),
mSilent(silent),
mReturnCode(0) {}
virtual ~runner() {}
int execute_cmd(
const std::string& command,
const std::vector<std::string>& args,
const std::string& working_dir,
const std::map<std::string, std::string>& env,
const bool silent,
const bool interactive,
const copySSHPtr& sshinfo = nullptr,
std::string* output = nullptr
);
virtual bool execute() = 0;
int return_code() const { return mReturnCode; }
protected:
std::string mCommand;
std::vector<std::string> mArgs;
std::string mWorkingDir;
std::map<std::string, std::string> mEnv;
bool mSilent;
int mReturnCode;
};
class runner_local : public runner {
public:
runner_local(std::string command, std::vector<std::string> args={}, std::string working_dir="", std::map<std::string, std::string> env={}, bool silent=false) :
runner(command, args, working_dir, env, silent) {}
bool execute() override;
};
class runner_local_interactive : public runner {
public:
runner_local_interactive(std::string command, std::vector<std::string> args={}, std::string working_dir="", std::map<std::string, std::string> env={}) :
runner(command, args, working_dir, env, false) {}
bool execute() override;
};
class runner_ssh : public runner {
public:
runner_ssh(const sSSHInfo sshinfo, std::string command, std::vector<std::string> args={}, std::string working_dir="", std::map<std::string, std::string> env={}, bool silent=false) :
runner(command, args, working_dir, env, silent), mSSHInfo(sshinfo) {}
bool execute() override;
protected:
const sSSHInfo mSSHInfo;
};
class runner_ssh_interactive : public runner_ssh {
public:
runner_ssh_interactive(const sSSHInfo sshinfo, std::string command, std::vector<std::string> args={}, std::string working_dir="", std::map<std::string, std::string> env={}, bool silent=true) :
runner_ssh(sshinfo, command, args, working_dir, env, silent) {}
bool execute() override;
};
class runner_ssh_capture : public runner_ssh {
public:
runner_ssh_capture(const sSSHInfo sshinfo, std::string output, std::string command, std::vector<std::string> args={}, std::string working_dir="", std::map<std::string, std::string> env={}) :
runner_ssh(sshinfo, command, args, working_dir, env, true), mOutput(output) {}
bool execute() override;
protected:
std::string & mOutput;
};
} // namespace runner