This commit is contained in:
parent
484613d10d
commit
bbc280a50a
@ -2,10 +2,10 @@
|
||||
#include "utils/directories.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "services.hpp"
|
||||
#include "contrib/base64.hpp"
|
||||
#include "templates.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/json.hpp"
|
||||
#include "utils/execute.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
@ -102,22 +102,12 @@ std::string server_env_manager::get_variable(const std::string& name) const {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Helper method implementations
|
||||
std::string server_env_manager::construct_ssh_cmd(bool allocateTTY) const {
|
||||
std::stringstream ssh_cmd;
|
||||
ssh_cmd << "ssh -p " << get_SSH_PORT() << " " << (allocateTTY ? "-tt " : "")
|
||||
<< get_SSH_USER() << "@" << get_SSH_HOST();
|
||||
return ssh_cmd.str();
|
||||
}
|
||||
|
||||
sCommand server_env_manager::construct_standard_command_run_cmd(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent) const
|
||||
sCommand server_env_manager::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent) const
|
||||
{
|
||||
if (command.empty())
|
||||
return sCommand();
|
||||
|
||||
std::string remote_service_template_path = remotepath::service_template(mServerName,service_name);
|
||||
std::string remote_service_config_path = remotepath::service_config(mServerName,service_name);
|
||||
|
||||
std::string script_path = remote_service_template_path + "/" + command + ".sh";
|
||||
|
||||
std::map<std::string, std::string> env_vars;
|
||||
@ -143,12 +133,12 @@ sCommand server_env_manager::construct_standard_command_run_cmd(const std::strin
|
||||
bool server_env_manager::check_remote_dir_exists(const std::string &dir_path) const
|
||||
{
|
||||
sCommand scommand("test -d " + quote(dir_path));
|
||||
return execute_ssh_command(scommand);
|
||||
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
|
||||
}
|
||||
|
||||
bool server_env_manager::check_remote_file_exists(const std::string& file_path) const {
|
||||
sCommand scommand("test -f " + quote(file_path));
|
||||
return execute_ssh_command(scommand);
|
||||
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
|
||||
}
|
||||
|
||||
bool server_env_manager::check_remote_items_exist(const std::vector<std::string> &file_paths) const
|
||||
@ -163,7 +153,7 @@ bool server_env_manager::check_remote_items_exist(const std::vector<std::string>
|
||||
// check if all items in the vector exist on the remote server, in a single command.
|
||||
sCommand scommand("for item in " + file_paths_str + "; do test -f $item; done");
|
||||
|
||||
bool okay = execute_ssh_command(scommand);
|
||||
bool okay = execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
|
||||
if (!okay) {
|
||||
std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl;
|
||||
return false;
|
||||
@ -171,152 +161,26 @@ bool server_env_manager::check_remote_items_exist(const std::vector<std::string>
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EXITSTATUSCHECK(int ret) {
|
||||
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
|
||||
}
|
||||
|
||||
bool server_env_manager::execute_ssh_command(const sCommand& command, bool allocateTTY) const {
|
||||
std::string full_cmd = construct_ssh_cmd(allocateTTY) + " " + (allocateTTY ? halfquote(command.construct_rawcmd()) : quote(command.construct_safecmd()));
|
||||
int ret = system(full_cmd.c_str());
|
||||
if (ret == -1) {
|
||||
std::cerr << "Error: Failed to execute command: " << full_cmd << std::endl;
|
||||
// system() failed to execute
|
||||
return false;
|
||||
} else {
|
||||
// Check if the command exited normally and with status 0
|
||||
return EXITSTATUSCHECK(ret);
|
||||
}
|
||||
}
|
||||
|
||||
bool server_env_manager::execute_ssh_command_and_capture_output(const sCommand& command, std::string &output, bool allocateTTY) const
|
||||
{
|
||||
std::string full_cmd = construct_ssh_cmd(allocateTTY) + " " + quote(command.construct_safecmd());
|
||||
return execute_local_command_and_capture_output(full_cmd, output);
|
||||
}
|
||||
|
||||
bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent) const
|
||||
{
|
||||
sCommand scommand = construct_standard_command_run_cmd(service_name, command, args, silent);
|
||||
sCommand scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
|
||||
if (scommand.get_command_to_run().empty())
|
||||
return false;
|
||||
bool allocateTTY = (command=="ssh");
|
||||
return execute_ssh_command(scommand, allocateTTY);
|
||||
cMode mode = (command=="ssh") ? cMode::Interactive : cMode::Silent;
|
||||
return execute_ssh_command(get_SSH_INFO(), scommand, mode);
|
||||
}
|
||||
|
||||
bool server_env_manager::run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command, std::vector<std::string> args, std::string &output, bool silent) const
|
||||
{
|
||||
sCommand scommand = construct_standard_command_run_cmd(service_name, command, args, silent);
|
||||
sCommand scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
|
||||
if (scommand.get_command_to_run().empty())
|
||||
return false;
|
||||
bool allocateTTY = (command=="ssh");
|
||||
return execute_ssh_command_and_capture_output(scommand, output, allocateTTY);
|
||||
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::CaptureOutput, &output);
|
||||
}
|
||||
|
||||
bool server_env_manager::execute_local_command(const sCommand& command) {
|
||||
if (command.get_command_to_run().empty())
|
||||
return false;
|
||||
int ret = system(command.construct_safecmd().c_str());
|
||||
return EXITSTATUSCHECK(ret);
|
||||
}
|
||||
|
||||
bool server_env_manager::execute_local_command_interactive(const sCommand &command)
|
||||
{
|
||||
if (command.get_command_to_run().empty())
|
||||
return false;
|
||||
std::string full_command = command.construct_rawcmd(); // Get the command string
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
// Fork failed
|
||||
perror("fork failed");
|
||||
return false;
|
||||
} else if (pid == 0) {
|
||||
// Child process
|
||||
std::vector<const char *> commandvec = {"bash", "-c", full_command.c_str(),NULL};
|
||||
|
||||
std::cout << "Executing command: ";
|
||||
for (auto & x : commandvec) std::cout << x << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
execvp(commandvec[0], const_cast<char* const*>(commandvec.data()));
|
||||
// If execvp returns, it means an error occurred
|
||||
perror("execvp failed");
|
||||
exit(EXIT_FAILURE); // Exit child process on error
|
||||
} else {
|
||||
// Parent process
|
||||
int ret;
|
||||
// Wait for the child process to complete
|
||||
waitpid(pid, &ret, 0);
|
||||
|
||||
return EXITSTATUSCHECK(ret);
|
||||
}
|
||||
}
|
||||
|
||||
bool server_env_manager::execute_local_command_and_capture_output(const sCommand& command, std::string &output)
|
||||
{
|
||||
if (command.get_command_to_run().empty())
|
||||
return false;
|
||||
std::string full_cmd = command.construct_safecmd() + " 2>&1";
|
||||
FILE *pipe = popen(full_cmd.c_str(), "r");
|
||||
if (!pipe) {
|
||||
return false;
|
||||
}
|
||||
char buffer[128];
|
||||
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||
output += buffer;
|
||||
}
|
||||
int ret = pclose(pipe);
|
||||
return EXITSTATUSCHECK(ret);
|
||||
}
|
||||
|
||||
|
||||
std::string sCommand::construct_safecmd() const
|
||||
{
|
||||
if (mCmd.empty())
|
||||
return "";
|
||||
std::string to_encode;
|
||||
|
||||
for (const auto& env_var : mVars) {
|
||||
to_encode += env_var.first + "=" + quote(dequote(trim(env_var.second))) + " ";
|
||||
}
|
||||
to_encode += mCmd;
|
||||
|
||||
std::string encoded = base64_encode(to_encode);
|
||||
|
||||
std::string commandstr = "echo " + encoded + " | base64 -d | bash";
|
||||
|
||||
if (!mDir.empty())
|
||||
commandstr = "cd " + quote(mDir) + " && " + commandstr;
|
||||
return commandstr;
|
||||
}
|
||||
|
||||
std::string sCommand::construct_rawcmd() const
|
||||
{
|
||||
if (mCmd.empty())
|
||||
return "";
|
||||
|
||||
std::string rawcmd;
|
||||
if (!mDir.empty())
|
||||
rawcmd = "cd " + quote(mDir) + " && ";
|
||||
|
||||
for (const auto& env_var : mVars) {
|
||||
rawcmd += env_var.first + "=" + quote(dequote(trim(env_var.second))) + " ";
|
||||
}
|
||||
rawcmd += mCmd;
|
||||
return rawcmd;
|
||||
}
|
||||
|
||||
// base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh"
|
||||
// echo YmFzaCAtYyAnRk9PPUJBUiBXSEVFPVlBWSBiYXNoIC4vdGVzdC5zaCcK | base64 -d | bash
|
||||
|
||||
std::string makesafecmd(const std::string &command)
|
||||
{
|
||||
if (command.empty())
|
||||
return "";
|
||||
std::string encoded = base64_encode(dequote(trim(command)));
|
||||
std::string commandstr = "echo " + encoded + " | base64 -d | bash";
|
||||
return commandstr;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
@ -9,35 +9,13 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "utils/execute.hpp"
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
// class to hold a command to run on the remote server.
|
||||
class sCommand {
|
||||
public:
|
||||
sCommand(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars) :
|
||||
mDir(directory_to_run_in), mCmd(command_to_run), mVars(env_vars) {}
|
||||
sCommand(std::string command_to_run) :
|
||||
mDir(""), mCmd(command_to_run), mVars({}) {}
|
||||
sCommand() : mDir(""), mCmd(""), mVars({}) {}
|
||||
class server_env_manager;
|
||||
|
||||
std::string get_directory_to_run_in() const { return mDir; }
|
||||
std::string get_command_to_run() const { return mCmd; }
|
||||
const std::map<std::string, std::string>& get_env_vars() const { return mVars; }
|
||||
|
||||
void add_env_var(const std::string& key, const std::string& value) { mVars[key] = value; }
|
||||
|
||||
std::string construct_safecmd() const;
|
||||
std::string construct_rawcmd() const;
|
||||
|
||||
bool empty() const { return mCmd.empty(); }
|
||||
|
||||
private:
|
||||
std::string mDir;
|
||||
std::string mCmd;
|
||||
std::map<std::string, std::string> mVars;
|
||||
};
|
||||
|
||||
std::string makesafecmd(const std::string& command);
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// reads path / server.env and provides a class to access the variables.
|
||||
// each env file is required to have the following variables:
|
||||
@ -65,6 +43,7 @@ class server_env_manager {
|
||||
std::string get_SSH_USER() const { return get_variable("SSH_USER"); }
|
||||
std::string get_SSH_PORT() const { return get_variable("SSH_PORT"); }
|
||||
std::string get_DROPSHELL_DIR() const { return get_variable("DROPSHELL_DIR"); }
|
||||
sSSHInfo get_SSH_INFO() const { return sSSHInfo{get_SSH_HOST(), get_SSH_USER(), get_SSH_PORT()}; }
|
||||
bool is_valid() const { return mValid; }
|
||||
std::string get_server_name() const { return mServerName; }
|
||||
|
||||
@ -77,17 +56,8 @@ class server_env_manager {
|
||||
bool run_remote_template_command(const std::string& service_name, const std::string& command, std::vector<std::string> args, bool silent=false) const;
|
||||
bool run_remote_template_command_and_capture_output(const std::string& service_name, const std::string& command, std::vector<std::string> args, std::string & output, bool silent=false) const;
|
||||
|
||||
public:
|
||||
bool execute_ssh_command(const sCommand& command, bool allocateTTY=false) const;
|
||||
bool execute_ssh_command_and_capture_output(const sCommand& command, std::string & output, bool allocateTTY=false) const;
|
||||
|
||||
static bool execute_local_command(const sCommand& command);
|
||||
static bool execute_local_command_interactive(const sCommand& command);
|
||||
static bool execute_local_command_and_capture_output(const sCommand& command, std::string & output);
|
||||
|
||||
private:
|
||||
std::string construct_ssh_cmd(bool allocateTTY=false) const;
|
||||
sCommand construct_standard_command_run_cmd(const std::string& service_name, const std::string& command, std::vector<std::string> args, bool silent) const;
|
||||
sCommand construct_standard_template_run_cmd(const std::string& service_name, const std::string& command, std::vector<std::string> args, bool silent) const;
|
||||
|
||||
private:
|
||||
std::string mServerName;
|
||||
|
@ -52,7 +52,7 @@ bool service_runner::install(bool silent) {
|
||||
// Create service directory
|
||||
std::string remote_service_path = remotepath::service(mServer, mService);
|
||||
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
|
||||
if (!mServerEnv.execute_ssh_command(mkdir_cmd))
|
||||
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(mkdir_cmd), cMode::Silent))
|
||||
{
|
||||
std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
|
||||
return false;
|
||||
@ -60,7 +60,7 @@ bool service_runner::install(bool silent) {
|
||||
|
||||
// Check if rsync is installed on remote host
|
||||
std::string check_rsync_cmd = "which rsync > /dev/null 2>&1";
|
||||
if (!mServerEnv.execute_ssh_command(check_rsync_cmd))
|
||||
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(check_rsync_cmd), cMode::Silent))
|
||||
{
|
||||
std::cerr << "rsync is not installed on the remote host" << std::endl;
|
||||
return false;
|
||||
@ -74,7 +74,7 @@ bool service_runner::install(bool silent) {
|
||||
mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" +
|
||||
quote(remotepath::service_template(mServer, mService)+"/");
|
||||
//std::cout << std::endl << rsync_cmd << std::endl << std::endl;
|
||||
if (!mServerEnv.execute_local_command(rsync_cmd))
|
||||
if (!execute_local_command(rsync_cmd, silent ? cMode::Silent : cMode::None))
|
||||
{
|
||||
std::cerr << "Failed to copy template files using rsync" << std::endl;
|
||||
std::cerr << "Is rsync installed on the remote host?" << std::endl;
|
||||
@ -94,7 +94,7 @@ bool service_runner::install(bool silent) {
|
||||
quote(local_service_path + "/") + " "+
|
||||
mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" +
|
||||
quote(remotepath::service_config(mServer,mService) + "/");
|
||||
if (!mServerEnv.execute_local_command(rsync_cmd))
|
||||
if (!execute_local_command(rsync_cmd, silent ? cMode::Silent : cMode::None))
|
||||
{
|
||||
std::cerr << "Failed to copy service files using rsync" << std::endl;
|
||||
return false;
|
||||
@ -138,7 +138,7 @@ bool service_runner::uninstall(bool silent) {
|
||||
|
||||
// 4. Remove the service directory from the server
|
||||
std::string rm_cmd = "rm -rf " + quote(remotepath::service(mServer, mService));
|
||||
if (!mServerEnv.execute_ssh_command(rm_cmd)) {
|
||||
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(rm_cmd), cMode::Silent)) {
|
||||
std::cerr << "Failed to remove service directory" << std::endl;
|
||||
return false;
|
||||
}
|
||||
@ -153,32 +153,16 @@ bool service_runner::nuke(bool silent)
|
||||
|
||||
if (!mServerEnv.is_valid()) return false; // should never hit this.
|
||||
|
||||
// 2. Check if service directory exists on server
|
||||
if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService)))
|
||||
std::cerr << "Service is not installed: " << mService << std::endl;
|
||||
else
|
||||
std::map<std::string, std::string> env_vars;
|
||||
if (!get_all_service_env_vars(mServer, mService, env_vars))
|
||||
std::cerr << "Warning: Failed to get all service env vars for " << mService << std::endl;
|
||||
|
||||
std::string remote_service_path = remotepath::service(mServer, mService);
|
||||
bool okay = mServerEnv.run_remote_template_command("dropshell-agent", "_nuke_other", {mService, remote_service_path}, silent);
|
||||
if (!okay)
|
||||
{
|
||||
// try uninstalling.
|
||||
if (gTemplateManager().template_command_exists(mServiceInfo.template_name, "uninstall"))
|
||||
if (!mServerEnv.run_remote_template_command(mService, "uninstall", {}))
|
||||
std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl;
|
||||
|
||||
// try nuke script.
|
||||
if (gTemplateManager().template_command_exists(mServiceInfo.template_name, "nuke"))
|
||||
if (!mServerEnv.run_remote_template_command(mService, "nuke", {}))
|
||||
std::cerr << "Warning: Nuke script failed, but continuing with directory removal" << std::endl;
|
||||
|
||||
// try rm -rf.
|
||||
std::string remotedir = remotepath::service(mServer, mService);
|
||||
std::string remoteparentdir = get_parent(remotedir);
|
||||
std::string remotechilddir = get_child(remotedir);
|
||||
|
||||
std::string cmd = quote("rm -rf /parent/"+remotechilddir);
|
||||
std::string rm_cmd = "docker run --rm -v "+quote(remoteparentdir)+":/parent debian bash -c "+cmd;
|
||||
if (!mServerEnv.execute_ssh_command(rm_cmd)) {
|
||||
std::cerr << "Failed to remove service directory" << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::cerr << "Warning: Nuke script failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "Service " << mService << " successfully nuked from " << mServer << std::endl;
|
||||
@ -206,7 +190,7 @@ bool service_runner::fullnuke()
|
||||
}
|
||||
|
||||
std::string rm_cmd = "rm -rf " + quote(local_service_path);
|
||||
if (!mServerEnv.execute_local_command(rm_cmd)) {
|
||||
if (!execute_local_command(rm_cmd, cMode::Silent)) {
|
||||
std::cerr << "Failed to remove service directory" << std::endl;
|
||||
return false;
|
||||
}
|
||||
@ -416,34 +400,20 @@ std::string service_runner::healthmark()
|
||||
return HealthStatus2String(status);
|
||||
}
|
||||
|
||||
void service_runner::interactive_ssh(const std::string & server_name, const std::string & command) {
|
||||
bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) {
|
||||
std::string serverpath = localpath::server(server_name);
|
||||
if (serverpath.empty()) {
|
||||
std::cerr << "Error: Server not found: " << server_name << std::endl;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
sCommand scommand("bash");
|
||||
server_env_manager env(server_name);
|
||||
if (!env.is_valid()) {
|
||||
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ssh_address = env.get_SSH_HOST();
|
||||
std::string ssh_user = env.get_SSH_USER();
|
||||
std::string ssh_port = env.get_SSH_PORT();
|
||||
|
||||
std::string login = ssh_user + "@" + ssh_address;
|
||||
|
||||
// Execute ssh with server_name and command
|
||||
if (command.empty())
|
||||
execlp("ssh", "ssh", "-tt", login.c_str(), "-p", ssh_port.c_str(), nullptr);
|
||||
else
|
||||
execlp("ssh", "ssh", "-tt", login.c_str(), "-p", ssh_port.c_str(), command.c_str(), nullptr);
|
||||
|
||||
// If exec returns, it means there was an error
|
||||
perror("ssh execution failed");
|
||||
exit(EXIT_FAILURE);
|
||||
return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
|
||||
}
|
||||
|
||||
void service_runner::edit_server(const std::string &server_name)
|
||||
@ -491,19 +461,19 @@ bool service_runner::edit_file(const std::string &file_path)
|
||||
}
|
||||
|
||||
std::cout << "Editing file: " << file_path << std::endl;
|
||||
return server_env_manager::execute_local_command_interactive(editor_cmd);
|
||||
return execute_local_command(editor_cmd, cMode::Interactive);
|
||||
}
|
||||
|
||||
void service_runner::interactive_ssh_service()
|
||||
bool service_runner::interactive_ssh_service()
|
||||
{
|
||||
std::set<std::string> used_commands = get_used_commands(mServer, mService);
|
||||
if (used_commands.find("ssh") == used_commands.end()) {
|
||||
std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> args; // not passed through yet.
|
||||
mServerEnv.run_remote_template_command(mService, "ssh", args);
|
||||
return mServerEnv.run_remote_template_command(mService, "ssh", args);
|
||||
}
|
||||
|
||||
void service_runner::edit_service_config()
|
||||
@ -598,7 +568,7 @@ bool service_runner::restore(std::string backup_file, bool silent)
|
||||
|
||||
// Copy backup file from local to server
|
||||
std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_backup_file_path) + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_backup_file_path) + (silent ? " > /dev/null 2>&1" : "");
|
||||
if (!mServerEnv.execute_local_command(scp_cmd)) {
|
||||
if (!execute_local_command(scp_cmd, silent ? cMode::Silent : cMode::None)) {
|
||||
std::cerr << "Failed to copy backup file from server" << std::endl;
|
||||
return false;
|
||||
}
|
||||
@ -676,7 +646,7 @@ bool service_runner::backup(bool silent) {
|
||||
std::string remote_backups_dir = remotepath::backups(mServer);
|
||||
if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
|
||||
std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
|
||||
if (!mServerEnv.execute_ssh_command(mkdir_cmd)) {
|
||||
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(mkdir_cmd), cMode::Silent)) {
|
||||
std::cerr << "Failed to create backups directory on server" << std::endl;
|
||||
return false;
|
||||
}
|
||||
@ -720,7 +690,7 @@ bool service_runner::backup(bool silent) {
|
||||
std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " +
|
||||
mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" +
|
||||
quote(remote_backup_file_path) + " " + quote(local_backup_file_path) + (silent ? " > /dev/null 2>&1" : "");
|
||||
if (!mServerEnv.execute_local_command(scp_cmd)) {
|
||||
if (!execute_local_command(scp_cmd, silent ? cMode::Silent : cMode::None)) {
|
||||
std::cerr << "Failed to copy backup file from server" << std::endl;
|
||||
return false;
|
||||
}
|
||||
@ -737,17 +707,16 @@ cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mSe
|
||||
{
|
||||
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10);
|
||||
std::string mkdir_cmd = "mkdir -p " + quote(p);
|
||||
if (!server_env.execute_ssh_command(mkdir_cmd))
|
||||
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand(mkdir_cmd), cMode::Silent))
|
||||
std::cerr << "Failed to create temp directory on server" << std::endl;
|
||||
else
|
||||
mPath = p;
|
||||
}
|
||||
|
||||
|
||||
cRemoteTempFolder::~cRemoteTempFolder()
|
||||
{
|
||||
std::string rm_cmd = "rm -rf " + quote(mPath);
|
||||
mServerEnv.execute_ssh_command(rm_cmd);
|
||||
execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(rm_cmd), cMode::Silent);
|
||||
}
|
||||
|
||||
std::string cRemoteTempFolder::path() const
|
||||
|
@ -83,7 +83,7 @@ class service_runner {
|
||||
|
||||
// launch an interactive ssh session on a server or service
|
||||
// replaces the current dropshell process with the ssh process
|
||||
void interactive_ssh_service();
|
||||
bool interactive_ssh_service();
|
||||
|
||||
// edit the service configuration file
|
||||
void edit_service_config();
|
||||
@ -92,7 +92,7 @@ class service_runner {
|
||||
public:
|
||||
// utility functions
|
||||
static std::string get_latest_backup_file(const std::string& server, const std::string& service);
|
||||
static void interactive_ssh(const std::string & server_name, const std::string & command);
|
||||
static bool interactive_ssh(const std::string & server_name, const std::string & command);
|
||||
static void edit_server(const std::string & server_name);
|
||||
static bool edit_file(const std::string & file_path);
|
||||
static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
|
||||
|
167
src/utils/execute.cpp
Normal file
167
src/utils/execute.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
#include "execute.hpp"
|
||||
#include "assert.hpp"
|
||||
#include "contrib/base64.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
bool EXITSTATUSCHECK(int ret) {
|
||||
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
|
||||
}
|
||||
|
||||
namespace dropshell {
|
||||
bool execute_local_command_interactive(const sCommand &command, bool silent)
|
||||
{
|
||||
if (command.get_command_to_run().empty())
|
||||
return false;
|
||||
std::string full_command = command.construct_cmd(cStyle::Raw); // Get the command string
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
// Fork failed
|
||||
perror("fork failed");
|
||||
return false;
|
||||
} else if (pid == 0) {
|
||||
// Child process
|
||||
std::vector<const char *> commandvec = {"bash", "-c", full_command.c_str(),NULL};
|
||||
|
||||
if (!silent) {
|
||||
std::cout << "Executing command: ";
|
||||
for (auto & x : commandvec) std::cout << x << " ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
execvp(commandvec[0], const_cast<char* const*>(commandvec.data()));
|
||||
// If execvp returns, it means an error occurred
|
||||
perror("execvp failed");
|
||||
exit(EXIT_FAILURE); // Exit child process on error
|
||||
} else {
|
||||
// Parent process
|
||||
int ret;
|
||||
// Wait for the child process to complete
|
||||
waitpid(pid, &ret, 0);
|
||||
|
||||
return EXITSTATUSCHECK(ret);
|
||||
}
|
||||
}
|
||||
|
||||
bool execute_local_command(const sCommand& command, bool silent, bool safe) {
|
||||
if (command.get_command_to_run().empty())
|
||||
return false;
|
||||
cStyle style = safe ? cStyle::Safe : cStyle::Raw;
|
||||
std::string full_cmd = command.construct_cmd(style) + " 2>&1" + (silent ? " > /dev/null" : "");
|
||||
int ret = system(full_cmd.c_str());
|
||||
return EXITSTATUSCHECK(ret);
|
||||
}
|
||||
|
||||
bool execute_local_command_and_capture_output(const sCommand& command, std::string &output, bool silent, bool safe)
|
||||
{
|
||||
if (command.get_command_to_run().empty())
|
||||
return false;
|
||||
cStyle style = safe ? cStyle::Safe : cStyle::Raw;
|
||||
std::string full_cmd = command.construct_cmd(style) + " 2>&1";
|
||||
FILE *pipe = popen(full_cmd.c_str(), "r");
|
||||
if (!pipe) {
|
||||
return false;
|
||||
}
|
||||
char buffer[128];
|
||||
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||
output += buffer;
|
||||
}
|
||||
int ret = pclose(pipe);
|
||||
return EXITSTATUSCHECK(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool execute_local_command(const sCommand & command, cMode mode, std::string * output /* = nullptr */)
|
||||
{
|
||||
if (mode & cMode::Interactive) {
|
||||
ASSERT_MSG((mode & cMode::CaptureOutput), "Interactive mode and capture output mode cannot be used together");
|
||||
ASSERT_MSG(output == nullptr, "Interactive mode and an output string cannot be used together");
|
||||
ASSERT_MSG(! (mode & cMode::SafeCommand), "Interactive mode and safe command mode cannot be used together");
|
||||
return execute_local_command_interactive(command, mode & cMode::Silent);
|
||||
}
|
||||
|
||||
if (mode & cMode::CaptureOutput) {
|
||||
ASSERT_MSG(output != nullptr, "Capture output mode requires an output string to be provided");
|
||||
return execute_local_command_and_capture_output(command, *output, mode & cMode::Silent, mode & cMode::SafeCommand);
|
||||
}
|
||||
|
||||
return execute_local_command(command, mode & cMode::Silent, mode & cMode::SafeCommand);
|
||||
}
|
||||
|
||||
bool execute_ssh_command(const sSSHInfo &ssh_info, const sCommand &command, cMode mode, std::string *output)
|
||||
{
|
||||
if (command.get_command_to_run().empty())
|
||||
return false;
|
||||
|
||||
ASSERT_MSG(!(mode & cMode::Interactive && mode & cMode::SafeCommand), "Safe command mode must not be used with Interactive mode");
|
||||
|
||||
std::stringstream ssh_cmd;
|
||||
ssh_cmd << "ssh -p " << ssh_info.port << " " << (mode & cMode::Interactive ? "-tt " : "")
|
||||
<< ssh_info.user << "@" << ssh_info.host;
|
||||
|
||||
std::string full_cmd = ssh_cmd.str() + " " + command.get_command_to_run();
|
||||
|
||||
sCommand ssh_command = {
|
||||
command.get_directory_to_run_in(),
|
||||
full_cmd,
|
||||
command.get_env_vars()
|
||||
};
|
||||
|
||||
bool rval = execute_local_command(ssh_command, mode, output);
|
||||
|
||||
if (!rval) {
|
||||
std::cerr <<std::endl<<std::endl;
|
||||
std::cerr << "Error: Failed to execute ssh command: { [" << ssh_command.get_directory_to_run_in() << "], [";
|
||||
std::cerr << ssh_command.get_command_to_run() << "], [";
|
||||
for (const auto& env_var : ssh_command.get_env_vars()) {
|
||||
std::cerr << env_var.first << "=" << env_var.second << ", ";
|
||||
}
|
||||
std::cerr << "] }" << std::endl;
|
||||
std::cerr <<std::endl<<std::endl;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
std::string makesafecmd(const std::string &command)
|
||||
{
|
||||
if (command.empty())
|
||||
return "";
|
||||
std::string encoded = base64_encode(dequote(trim(command)));
|
||||
std::string commandstr = "echo " + encoded + " | base64 -d | bash";
|
||||
return commandstr;
|
||||
}
|
||||
|
||||
std::string sCommand::construct_cmd(cStyle style) const
|
||||
{
|
||||
if (mCmd.empty())
|
||||
return "";
|
||||
|
||||
std::string cdcmd;
|
||||
if (!mDir.empty())
|
||||
cdcmd = "cd " + quote(mDir) + " && ";
|
||||
|
||||
std::string cmdstr;
|
||||
for (const auto& env_var : mVars) {
|
||||
cmdstr += env_var.first + "=" + quote(dequote(trim(env_var.second))) + " ";
|
||||
}
|
||||
|
||||
cmdstr += mCmd;
|
||||
|
||||
if (is_safe(style))
|
||||
cmdstr = makesafecmd(cmdstr);
|
||||
|
||||
return cdcmd + cmdstr;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
89
src/utils/execute.hpp
Normal file
89
src/utils/execute.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef EXECUTE_HPP
|
||||
#define EXECUTE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
class sCommand;
|
||||
|
||||
// mode bitset
|
||||
enum class cMode {
|
||||
None = 0,
|
||||
Interactive = 1,
|
||||
Silent = 2,
|
||||
CaptureOutput = 4,
|
||||
SafeCommand = 8
|
||||
};
|
||||
|
||||
typedef struct sSSHInfo {
|
||||
std::string host;
|
||||
std::string user;
|
||||
std::string port;
|
||||
} sSSHInfo;
|
||||
|
||||
bool execute_local_command(const sCommand & command, cMode mode = cMode::None, std::string * output = nullptr);
|
||||
bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & command, cMode mode = cMode::None, std::string * output = nullptr);
|
||||
|
||||
std::string makesafecmd(const std::string& command);
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
enum class cStyle {
|
||||
Safe = 0,
|
||||
Raw = 1
|
||||
};
|
||||
|
||||
inline bool is_safe(cStyle style) { return style == cStyle::Safe; }
|
||||
|
||||
// class to hold a command to run on the remote server.
|
||||
class sCommand {
|
||||
public:
|
||||
sCommand(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars) :
|
||||
mDir(directory_to_run_in), mCmd(command_to_run), mVars(env_vars) {}
|
||||
sCommand(std::string command_to_run) :
|
||||
mDir(""), mCmd(command_to_run), mVars({}) {}
|
||||
sCommand() : mDir(""), mCmd(""), mVars({}) {}
|
||||
|
||||
std::string get_directory_to_run_in() const { return mDir; }
|
||||
std::string get_command_to_run() const { return mCmd; }
|
||||
const std::map<std::string, std::string>& get_env_vars() const { return mVars; }
|
||||
|
||||
void add_env_var(const std::string& key, const std::string& value) { mVars[key] = value; }
|
||||
|
||||
|
||||
std::string construct_cmd(cStyle style) const;
|
||||
|
||||
bool empty() const { return mCmd.empty(); }
|
||||
|
||||
private:
|
||||
std::string mDir;
|
||||
std::string mCmd;
|
||||
std::map<std::string, std::string> mVars;
|
||||
};
|
||||
|
||||
|
||||
// Bitwise AND operator for cMode
|
||||
inline bool operator&(cMode lhs, cMode rhs) {
|
||||
return (
|
||||
static_cast<int>(lhs) & static_cast<int>(rhs) != 0
|
||||
);
|
||||
}
|
||||
|
||||
inline cMode operator-(cMode lhs, cMode rhs) {
|
||||
return static_cast<cMode>(
|
||||
static_cast<int>(lhs) & ~static_cast<int>(rhs)
|
||||
);
|
||||
}
|
||||
|
||||
inline cMode operator|(cMode lhs, cMode rhs) {
|
||||
return static_cast<cMode>(static_cast<int>(lhs) | static_cast<int>(rhs));
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
||||
|
||||
|
||||
|
||||
#endif
|
@ -80,6 +80,11 @@ std::string halfquote(std::string str)
|
||||
return "'" + str + "'";
|
||||
}
|
||||
|
||||
std::string escapequotes(std::string str)
|
||||
{
|
||||
return std::regex_replace(str, std::regex("\""), "\\\"");
|
||||
}
|
||||
|
||||
std::string multi2string(std::vector<std::string> values)
|
||||
{
|
||||
std::string result;
|
||||
|
@ -21,6 +21,7 @@ std::string dequote(std::string str);
|
||||
std::string quote(std::string str);
|
||||
std::string halfquote(std::string str);
|
||||
std::string requote(std::string str);
|
||||
std::string escapequotes(std::string str);
|
||||
|
||||
std::string multi2string(std::vector<std::string> values);
|
||||
std::vector<std::string> string2multi(std::string values);
|
||||
|
25
templates/dropshell-agent/_nuke_other.sh
Normal file
25
templates/dropshell-agent/_nuke_other.sh
Normal file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
source "$SCRIPT_DIR/shared/_common.sh"
|
||||
|
||||
|
||||
A_SERVICE="$1"
|
||||
A_SERVICE_PATH="$2"
|
||||
|
||||
|
||||
# 1. Check if service directory exists on server
|
||||
[ -d "$A_SERVICE_PATH" ] || _die "Service is not installed: $A_SERVICE"
|
||||
|
||||
# uninstall the service
|
||||
if [ -f "$A_SERVICE_PATH/uninstall.sh" ]; then
|
||||
$A_SERVICE_PATH/uninstall.sh
|
||||
fi
|
||||
|
||||
# nuke the service
|
||||
if [ -f "$A_SERVICE_PATH/nuke.sh" ]; then
|
||||
$A_SERVICE_PATH/nuke.sh
|
||||
fi
|
||||
|
||||
# remove the service directory
|
||||
rm -rf "$A_SERVICE_PATH"
|
Loading…
x
Reference in New Issue
Block a user