diff --git a/CMakeLists.txt b/CMakeLists.txt index 40bd14f..fcadf8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,6 @@ configure_file( ) # Find required packages -find_package(Curses REQUIRED) find_package(TBB REQUIRED) # Auto-detect source files @@ -42,12 +41,10 @@ add_executable(dropshell ${SOURCES}) target_include_directories(dropshell PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}/src - ${CURSES_INCLUDE_DIRS} ) # Link libraries target_link_libraries(dropshell PRIVATE - ${CURSES_LIBRARIES} TBB::tbb ) diff --git a/src/contrib/base64.cpp b/src/contrib/base64.cpp new file mode 100644 index 0000000..494742f --- /dev/null +++ b/src/contrib/base64.cpp @@ -0,0 +1,282 @@ +/* + base64.cpp and base64.h + + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + + Version: 2.rc.09 (release candidate) + + Copyright (C) 2004-2017, 2020-2022 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "contrib/base64.hpp" + +#include +#include + + // + // Depending on the url parameter in base64_chars, one of + // two sets of base64 characters needs to be chosen. + // They differ in their last two characters. + // +static const char* base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"}; + +static unsigned int pos_of_char(const unsigned char chr) { + // + // Return the position of chr within base64_encode() + // + + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' + else + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + throw std::runtime_error("Input is not valid base64-encoded data."); +} + +static std::string insert_linebreaks(std::string str, size_t distance) { + // + // Provided by https://github.com/JomaCorpFX, adapted by me. + // + if (!str.length()) { + return ""; + } + + size_t pos = distance; + + while (pos < str.size()) { + str.insert(pos, "\n"); + pos += distance + 1; + } + + return str; +} + +template +static std::string encode_with_line_breaks(String s) { + return insert_linebreaks(base64_encode(s, false), line_length); +} + +template +static std::string encode_pem(String s) { + return encode_with_line_breaks(s); +} + +template +static std::string encode_mime(String s) { + return encode_with_line_breaks(s); +} + +template +static std::string encode(String s, bool url) { + return base64_encode(reinterpret_cast(s.data()), s.length(), url); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { + + size_t len_encoded = (in_len +2) / 3 * 4; + + unsigned char trailing_char = url ? '.' : '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = base64_chars[url]; + + std::string ret; + ret.reserve(len_encoded); + + unsigned int pos = 0; + + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos+1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos+2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { + + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } + + pos += 3; + } + + + return ret; +} + +template +static std::string decode(String const& encoded_string, bool remove_linebreaks) { + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // + + if (encoded_string.empty()) return std::string(); + + if (remove_linebreaks) { + + std::string copy(encoded_string); + + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + + return base64_decode(copy, false); + } + + size_t length_of_string = encoded_string.length(); + size_t pos = 0; + + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string) { + // + // Iterate over encoded input string in chunks. The size of all + // chunks except the last one is 4 bytes. + // + // The last chunk might be padded with equal signs or dots + // in order to make it 4 bytes in size as well, but this + // is not required as per RFC 2045. + // + // All chunks except the last one produce three output bytes. + // + // The last chunk produces at least one and up to three bytes. + // + + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) ); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); + + if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos+2) != '=' && + encoded_string.at(pos+2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) + { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) ); + ret.push_back(static_cast( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); + + if ( ( pos + 3 < length_of_string ) && + encoded_string.at(pos+3) != '=' && + encoded_string.at(pos+3) != '.' + ) + { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3)) )); + } + } + + pos += 4; + } + + return ret; +} + +std::string base64_decode(std::string const& s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); +} + +std::string base64_encode(std::string const& s, bool url) { + return encode(s, url); +} + +std::string base64_encode_pem (std::string const& s) { + return encode_pem(s); +} + +std::string base64_encode_mime(std::string const& s) { + return encode_mime(s); +} + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// + +std::string base64_encode(std::string_view s, bool url) { + return encode(s, url); +} + +std::string base64_encode_pem(std::string_view s) { + return encode_pem(s); +} + +std::string base64_encode_mime(std::string_view s) { + return encode_mime(s); +} + +std::string base64_decode(std::string_view s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); +} + +#endif // __cplusplus >= 201703L diff --git a/src/contrib/base64.hpp b/src/contrib/base64.hpp new file mode 100644 index 0000000..4860d63 --- /dev/null +++ b/src/contrib/base64.hpp @@ -0,0 +1,35 @@ +// +// base64 encoding and decoding with C++. +// Version: 2.rc.09 (release candidate) +// + +#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A +#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A + +#include + +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + +std::string base64_encode (std::string const& s, bool url = false); +std::string base64_encode_pem (std::string const& s); +std::string base64_encode_mime(std::string const& s); + +std::string base64_decode(std::string const& s, bool remove_linebreaks = false); +std::string base64_encode(unsigned char const*, size_t len, bool url = false); + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// +std::string base64_encode (std::string_view s, bool url = false); +std::string base64_encode_pem (std::string_view s); +std::string base64_encode_mime(std::string_view s); + +std::string base64_decode(std::string_view s, bool remove_linebreaks = false); +#endif // __cplusplus >= 201703L + +#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ diff --git a/src/main_commands.cpp b/src/main_commands.cpp index a3afe8b..902efe5 100644 --- a/src/main_commands.cpp +++ b/src/main_commands.cpp @@ -203,7 +203,8 @@ int backup(const std::vector & args, bool silent) { std::string remote_backups_dir = get_remote_backups_path(server_name); if (!silent) std::cout << "Remote backups directory on "<< server_name <<": " << remote_backups_dir << std::endl; std::string mkdir_cmd = "'mkdir -p " + quote(remote_backups_dir) + "'"; - if (!env.execute_ssh_command(mkdir_cmd, "Failed to create backups directory on server")) { + if (!env.execute_ssh_command(mkdir_cmd)) { + std::cerr << "Failed to create backups directory on server" << std::endl; return false; } @@ -235,20 +236,17 @@ int backup(const std::vector & args, bool silent) { ASSERT(3 == count_substring(magic_string, local_backup_file_path)); // Run backup script - std::string backup_cmd = "'cd " + quote(remote_service_template_path) + - " && /bin/bash "+quote(remote_command_script_file)+" "+quote(remote_service_config_path)+" "+ - quote(remote_backup_file_path)+"'"+ (silent ? " > /dev/null 2>&1" : ""); - - if (!env.execute_ssh_command(backup_cmd, "Backup script failed")) { - std::cerr << "Backup script failed: " << backup_cmd << std::endl; + if (!env.run_remote_template_command(service_name, command, {remote_backup_file_path}, silent)) { + std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; return false; - } + } // Copy backup file from server to local std::string scp_cmd = "scp -P " + env.get_SSH_PORT() + " " + env.get_SSH_USER() + "@" + env.get_SSH_HOST() + ":" + quote(remote_backup_file_path) + " " + quote(local_backup_file_path) + (silent ? " > /dev/null 2>&1" : ""); - if (!env.execute_local_command(scp_cmd, "Failed to copy backup file from server")) { + if (!env.execute_local_command(scp_cmd)) { + std::cerr << "Failed to copy backup file from server" << std::endl; return false; } diff --git a/src/server_env.cpp b/src/server_env.cpp index 1859009..38980c8 100644 --- a/src/server_env.cpp +++ b/src/server_env.cpp @@ -3,15 +3,13 @@ #include "utils/directories.hpp" #include "utils/utils.hpp" #include "services.hpp" +#include "contrib/base64.hpp" #include #include #include -namespace dropshell { -bool server_env::is_valid() const { - return mValid; -} +namespace dropshell { server_env::server_env(const std::string& server_name) : mValid(false), mServer_name(server_name) { if (server_name.empty()) @@ -62,27 +60,6 @@ std::string server_env::get_variable(const std::string& name) const { return m_env_manager->get_variable_substituted(name); } -const std::map &server_env::get_variables() const -{ - return variables; -} - -std::string server_env::get_SSH_HOST() const { - return get_variable("SSH_HOST"); -} - -std::string server_env::get_SSH_USER() const { - return get_variable("SSH_USER"); -} - -std::string server_env::get_SSH_PORT() const { - return get_variable("SSH_PORT"); -} - -std::string server_env::get_DROPSHELL_DIR() const { - return get_variable("DROPSHELL_DIR"); -} - // Helper method implementations std::string server_env::construct_ssh_cmd() const { std::stringstream ssh_cmd; @@ -91,34 +68,38 @@ std::string server_env::construct_ssh_cmd() const { return ssh_cmd.str(); } -std::string server_env::construct_standard_command_run_cmd(const std::string &service_name, const std::string &command, std::vector args) const +std::string server_env::construct_standard_command_run_cmd(const std::string &service_name, const std::string &command, std::vector args, bool silent) const { std::string remote_service_template_path = get_remote_service_template_path(mServer_name,service_name); std::string remote_service_config_path = get_remote_service_config_path(mServer_name,service_name); std::string script_path = remote_service_template_path + "/" + command + ".sh"; - std::string run_cmd = "'cd " + quote(remote_service_template_path) + - " && /bin/bash "+quote(script_path)+" "+quote(remote_service_config_path)+"'"; + + std::map env_vars; + envmanager env_manager(get_local_service_env_path(mServer_name,service_name)); + env_manager.load(); + env_manager.get_all_variables(env_vars); + env_vars["CONFIG_PATH"] = remote_service_config_path; + + std::string argstr = quote(remote_service_config_path); + for (const auto& arg : args) { + argstr += " " + quote(dequote(trim(arg))); + } + + sCommand scommand(remote_service_template_path, "bash " + quote(script_path) + " " + argstr + (silent ? " > /dev/null 2>&1" : ""), env_vars); + std::string run_cmd = scommand.construct_safecmd(); return run_cmd; } bool server_env::check_remote_dir_exists(const std::string &dir_path) const { - std::string check_dir_cmd = construct_ssh_cmd() + "'test -d " + quote(dir_path) + "'"; - if (system(check_dir_cmd.c_str()) != 0) { - std::cerr << "Error: Directory not found on remote server:" << std::filesystem::path(dir_path).filename().string() << std::endl; - return false; - } - return true; + sCommand scommand("test -d " + quote(dir_path)); + return execute_ssh_command(scommand); } bool server_env::check_remote_file_exists(const std::string& file_path) const { - std::string check_cmd = construct_ssh_cmd() + "'test -f " + quote(file_path) + "'"; - if (system(check_cmd.c_str()) != 0) { - std::cerr << "Error: File not found on remote server: " << std::filesystem::path(file_path).filename().string() << std::endl; - return false; - } - return true; + sCommand scommand("test -f " + quote(file_path)); + return execute_ssh_command(scommand); } bool server_env::check_remote_items_exist(const std::vector &file_paths) const @@ -131,55 +112,74 @@ bool server_env::check_remote_items_exist(const std::vector &file_p file_names_str += std::filesystem::path(file_path).filename().string() + " "; } // check if all items in the vector exist on the remote server, in a single command. - std::string check_cmd = construct_ssh_cmd() + "'for item in " + file_paths_str + "; do test -f $item; done'"; - if (system(check_cmd.c_str()) != 0) { + sCommand scommand("for item in " + file_paths_str + "; do test -f $item; done"); + + bool okay = execute_ssh_command(scommand); + if (!okay) { std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl; return false; } return true; } -bool server_env::execute_ssh_command(const std::string& command, const std::string& error_msg) const { - std::string full_cmd = construct_ssh_cmd() + command; - return execute_local_command(full_cmd, error_msg); +bool server_env::execute_ssh_command(const sCommand& command) const { + std::string full_cmd = construct_ssh_cmd() + command.construct_safecmd(); + return execute_local_command(full_cmd); } -bool server_env::execute_ssh_command_and_capture_output(const std::string &command, std::string &output, const std::string &error_msg) const +bool server_env::execute_ssh_command_and_capture_output(const sCommand& command, std::string &output) const { - std::string full_cmd = construct_ssh_cmd() + command; + std::string full_cmd = construct_ssh_cmd() + command.construct_safecmd(); return execute_local_command_and_capture_output(full_cmd, output); } -bool server_env::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector args, const std::string &error_msg) const +bool server_env::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector args, bool silent) const { - std::string full_cmd = construct_standard_command_run_cmd(service_name, command, args); - return execute_ssh_command(full_cmd, error_msg); + std::string full_cmd = construct_standard_command_run_cmd(service_name, command, args, silent); + return execute_ssh_command(full_cmd); } -bool server_env::execute_local_command(const std::string& command, const std::string& error_msg) { - bool okay = (system(command.c_str()) == 0); - - if (!okay && !error_msg.empty()) - std::cerr << "Error: " << error_msg << std::endl; +bool server_env::execute_local_command(const sCommand& command) { + bool okay = (system(command.construct_safecmd().c_str()) == 0); return okay; } -bool server_env::execute_local_command_and_capture_output(const std::string &command, std::string &output, const std::string& error_msg) +bool server_env::execute_local_command_and_capture_output(const sCommand& command, std::string &output) { - std::string full_cmd = command + " 2>&1"; + std::string full_cmd = command.construct_safecmd() + " 2>&1"; FILE *pipe = popen(full_cmd.c_str(), "r"); if (!pipe) { - if (!error_msg.empty()) - std::cerr << "Error: " << error_msg << std::endl; return false; } char buffer[128]; while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { output += buffer; } - pclose(pipe); - return true; + int status = pclose(pipe); + return (status == 0); } +std::string sCommand::construct_safecmd() const +{ + 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; +} + +// base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh" +// echo YmFzaCAtYyAnRk9PPUJBUiBXSEVFPVlBWSBiYXNoIC4vdGVzdC5zaCcK | base64 -d | bash + + } // namespace dropshell \ No newline at end of file diff --git a/src/server_env.hpp b/src/server_env.hpp index 589d2a2..8a86455 100644 --- a/src/server_env.hpp +++ b/src/server_env.hpp @@ -11,6 +11,29 @@ #include "utils/envmanager.hpp" namespace dropshell { +class sCommand { + public: + sCommand(std::string directory_to_run_in, std::string command_to_run, const std::map & 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({}) {} + + + std::string get_directory_to_run_in() const { return mDir; } + std::string get_command_to_run() const { return mCmd; } + const std::map& 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; + + private: + std::string mDir; + std::string mCmd; + std::map mVars; +}; + + // reads path / server.env and provides a class to access the variables. // each env file is required to have the following variables: // SSH_HOST @@ -22,34 +45,33 @@ class server_env { public: server_env(const std::string& server_name); std::string get_variable(const std::string& name) const; - const std::map& get_variables() const; - std::string get_SSH_HOST() const; - std::string get_SSH_USER() const; - std::string get_SSH_PORT() const; - - std::string get_DROPSHELL_DIR() const; - - bool is_valid() const; + // trivial getters. + const std::map& get_variables() const { return variables; } + std::string get_SSH_HOST() const { return get_variable("SSH_HOST"); } + 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"); } + bool is_valid() const { return mValid; } // helper functions public: - bool check_remote_items_exist(const std::vector& file_paths) const; - bool run_remote_template_command(const std::string& service_name, const std::string& command, std::vector args, const std::string& error_msg="") const; - - bool execute_ssh_command(const std::string& command, const std::string& error_msg="") const; - bool execute_ssh_command_and_capture_output(const std::string& command, std::string & output, const std::string& error_msg="") const; - - static bool execute_local_command(const std::string& command, const std::string& error_msg=""); - static bool execute_local_command_and_capture_output(const std::string& command, std::string & output, const std::string& error_msg=""); - bool check_remote_dir_exists(const std::string &dir_path) const; bool check_remote_file_exists(const std::string& file_path) const; + bool check_remote_items_exist(const std::vector& file_paths) const; + + bool run_remote_template_command(const std::string& service_name, const std::string& command, std::vector args, bool silent=false) const; + + public: + bool execute_ssh_command(const sCommand& command) const; + bool execute_ssh_command_and_capture_output(const sCommand& command, std::string & output) const; + + static bool execute_local_command(const sCommand& command); + static bool execute_local_command_and_capture_output(const sCommand& command, std::string & output); private: std::string construct_ssh_cmd() const; - std::string construct_standard_command_run_cmd(const std::string& service_name, const std::string& command, std::vector args) const; - + std::string construct_standard_command_run_cmd(const std::string& service_name, const std::string& command, std::vector args, bool silent) const; private: std::string mServer_name; diff --git a/src/service_runner.cpp b/src/service_runner.cpp index 6a032a4..343e8df 100644 --- a/src/service_runner.cpp +++ b/src/service_runner.cpp @@ -50,14 +50,20 @@ bool service_runner::install() { return false; // Create service directory - std::string mkdir_cmd = "'mkdir -p " + quote(mRemote_service_path) + "'"; - if (!m_server_env.execute_ssh_command(mkdir_cmd, "Failed to create service directory")) + std::string mkdir_cmd = "mkdir -p " + quote(mRemote_service_path); + if (!m_server_env.execute_ssh_command(mkdir_cmd)) + { + std::cerr << "Failed to create service directory" << std::endl; return false; + } // Check if rsync is installed on remote host - std::string check_rsync_cmd = "'which rsync > /dev/null 2>&1'"; - if (!m_server_env.execute_ssh_command(check_rsync_cmd, "rsync is not installed on the remote host")) + std::string check_rsync_cmd = "which rsync > /dev/null 2>&1"; + if (!m_server_env.execute_ssh_command(check_rsync_cmd)) + { + std::cerr << "rsync is not installed on the remote host" << std::endl; return false; + } // Copy template files { @@ -67,7 +73,12 @@ bool service_runner::install() { m_server_env.get_SSH_USER() + "@" + m_server_env.get_SSH_HOST() + ":" + quote(mRemote_service_template_path+"/"); //std::cout << std::endl << rsync_cmd << std::endl << std::endl; - m_server_env.execute_local_command(rsync_cmd,"Failed to copy template files"); + if (!m_server_env.execute_local_command(rsync_cmd)) + { + std::cerr << "Failed to copy template files using rsync" << std::endl; + std::cerr << "Is rsync installed on the remote host?" << std::endl; + return false; + } } // Copy service files (including service.env) @@ -82,7 +93,11 @@ bool service_runner::install() { quote(local_service_path + "/") + " "+ m_server_env.get_SSH_USER() + "@" + m_server_env.get_SSH_HOST() + ":" + quote(mRemote_service_config_path + "/"); - m_server_env.execute_local_command(rsync_cmd,"Failed to copy service files"); + if (!m_server_env.execute_local_command(rsync_cmd)) + { + std::cerr << "Failed to copy service files using rsync" << std::endl; + return false; + } } // Run install script @@ -124,7 +139,8 @@ bool service_runner::uninstall() { // 4. Remove the service directory from the server std::string rm_cmd = "'rm -rf " + quote(mRemote_service_path) + "'"; - if (!m_server_env.execute_ssh_command(rm_cmd, "Failed to remove service directory")) { + if (!m_server_env.execute_ssh_command(rm_cmd)) { + std::cerr << "Failed to remove service directory" << std::endl; return false; } diff --git a/src/templates.cpp b/src/templates.cpp index a5fb0ce..b2fc501 100644 --- a/src/templates.cpp +++ b/src/templates.cpp @@ -33,7 +33,7 @@ bool get_templates(std::vector& templates) { auto it = std::find_if(templates.begin(), templates.end(), [&info](const template_info& t) { return t.template_name == info.template_name; }); duplicate = (it!=templates.end()); - duplicate |= (info.template_name=="example"); // don't include the example template! + //duplicate |= (info.template_name=="example"); // don't include the example template! if (!duplicate) { templates.push_back(info);