From 630a9fd19a5446e45cf55fd5d8449fd0e804b93a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 18 May 2025 15:41:51 +1200 Subject: [PATCH] Add backupdata --- source/src/commands/backupdata.cpp | 172 ++++++++ source/src/commands/shared_commands.cpp | 509 ++++++++++++++---------- source/src/commands/shared_commands.hpp | 23 ++ source/src/service_runner.cpp | 24 +- source/src/utils/utils.cpp | 10 + source/src/utils/utils.hpp | 2 + 6 files changed, 510 insertions(+), 230 deletions(-) create mode 100644 source/src/commands/backupdata.cpp diff --git a/source/src/commands/backupdata.cpp b/source/src/commands/backupdata.cpp new file mode 100644 index 0000000..dadf175 --- /dev/null +++ b/source/src/commands/backupdata.cpp @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include + +#include "utils/output.hpp" +#include "utils/assert.hpp" +#include "utils/utils.hpp" +#include "command_registry.hpp" +#include "config.hpp" +#include "services.hpp" +#include "servers.hpp" +#include "server_env_manager.hpp" +#include "templates.hpp" +#include "utils/directories.hpp" +#include "shared_commands.hpp" + + +namespace dropshell { + +int backupdata_handler(const CommandContext& ctx); + +static std::vector backupdata_name_list={"backupdata","bd","backup","bup"}; + +// Static registration +struct BackupDataCommandRegister { + BackupDataCommandRegister() { + CommandRegistry::instance().register_command({ + backupdata_name_list, + backupdata_handler, + shared_commands::std_autocomplete_allowall, + false, // hidden + true, // requires_config + true, // requires_install + 0, // min_args (after command) + 1, // max_args (after command) + "backupdata SERVER SERVICE", + "Backup data for a service on a server.", + // heredoc + R"( + backupdata SERVER SERVICE Backup data for a service on a server. + backupdata SERVER all Backup data for all services on a server. + + Note: This command will not create any data or configuration. + It will simply backup the data on the remote server, saving it to a local file. + Restore the data with restore. + )" + }); + } +} backupdata_command_register; + + +bool backupdata_service(const std::string& server, const std::string& service) +{ + server_env_manager server_env(server); + if (!server_env.is_valid()) + { + error << "Server " << server << " is not valid" << std::endl; + return false; + } + + LocalServiceInfo sinfo = get_service_info(server, service); + if (!SIvalid(sinfo)) + { + error << "Service " << service << " is not valid" << std::endl; + return false; + } + + const std::string command = "backup"; + + if (!gTemplateManager().template_command_exists(sinfo.template_name, command)) { + info << service << " has no data to backup" << std::endl; + debug << "(no backup script for " << sinfo.template_name << ")" << std::endl; + return true; // nothing to back up. + } + + // Check if basic installed stuff is in place. + std::string remote_service_template_path = remotepath::service_template(server, service); + std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh"; + std::string remote_service_config_path = remotepath::service_config(server, service); + if (!server_env.check_remote_items_exist({ + remotepath::service(server, service), + remote_command_script_file, + remotefile::service_env(server, service)}) + ) + { + error << "Error: Required service directories not found on remote server" << std::endl; + info << "Is the service installed?" << std::endl; + return false; + } + + // Create backups directory on server if it doesn't exist + std::string remote_backups_dir = remotepath::backups(server); + debug << "Remote backups directory on "<< server <<": " << remote_backups_dir << std::endl; + std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir); + if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Defaults)) { + error << "Failed to create backups directory on server" << std::endl; + return false; + } + + // Create backups directory locally if it doesn't exist + std::string local_backups_dir = gConfig().get_local_backup_path(); + if (local_backups_dir.empty()) { + error << "Error: Local backups directory not found" << std::endl; + info << "Run 'dropshell edit' to configure DropShell" << std::endl; + return false; + } + if (!std::filesystem::exists(local_backups_dir)) + std::filesystem::create_directories(local_backups_dir); + + // Get current datetime for backup filename + shared_commands::cBackupFileName backup_filename_construction(server, service, sinfo.template_name); + if (!backup_filename_construction.is_valid()) { + error << "Invalid backup filename" << std::endl; + return false; + } + + // Construct backup filename + std::string backup_filename = backup_filename_construction.get_filename(); + std::string remote_backup_file_path = remote_backups_dir + "/" + backup_filename; + std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string(); + + // assert that the backup filename is valid - -_- appears exactly 3 times in local_backup_file_path. + ASSERT(3 == count_substring(magic_string(), local_backup_file_path), "Invalid backup filename"); + + { // Run backup script + shared_commands::cRemoteTempFolder remote_temp_folder(server_env); + if (!server_env.run_remote_template_command(service, command, {}, false, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) { + error << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; + return false; + } + + // Copy backup file from server to local + if (!shared_commands::scp_file_from_remote(server_env, remote_backup_file_path, local_backup_file_path, false)) { + error << "Failed to copy backup file from server" << std::endl; + return false; + } + } // dtor of remote_temp_folder will clean up the temp folder on the server + + info << "Backup created successfully. Restore with:"< services = get_server_services_info(server); + for (const auto &service : services) + okay &= backupdata_service(server, service.service_name); + return okay ? 0 : 1; + } + + std::string service = safearg(ctx.args, 1); + return backupdata_service(server, service); +} + +} // namespace dropshell \ No newline at end of file diff --git a/source/src/commands/shared_commands.cpp b/source/src/commands/shared_commands.cpp index 9694b23..6863b9d 100644 --- a/source/src/commands/shared_commands.cpp +++ b/source/src/commands/shared_commands.cpp @@ -13,239 +13,318 @@ namespace dropshell namespace shared_commands { - // ------------------------------------------------------------------------------------------------ - // std_autocomplete : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - void std_autocomplete(const CommandContext &ctx) - { - if (ctx.args.size() == 0) - { // just the command, no args yet. - // list servers - std::vector servers = get_configured_servers(); - for (const auto &server : servers) - { - rawout << server.name << std::endl; - } - } - else if (ctx.args.size() == 1) + // ------------------------------------------------------------------------------------------------ + // std_autocomplete : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + void std_autocomplete(const CommandContext &ctx) { - // list services - std::vector services = get_server_services_info(ctx.args[0]); - for (const auto &service : services) - { - rawout << service.service_name << std::endl; - } - } - } - - // ------------------------------------------------------------------------------------------------ - // std_autocomplete_allowall : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - void std_autocomplete_allowall(const CommandContext &ctx) - { - std_autocomplete(ctx); - if (ctx.args.size() == 1) - rawout << "all" << std::endl; - } - - // ------------------------------------------------------------------------------------------------ - // rsync_tree_to_remote : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - bool rsync_tree_to_remote( - const std::string &local_path, - const std::string &remote_path, - server_env_manager &server_env, - bool silent) - { - ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync."); - - std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " + - quote(local_path + "/") + " " + - quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + - remote_path + "/"); - return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); - } - - // ------------------------------------------------------------------------------------------------ - // get_arch : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - std::string get_arch() - { - // determine the architecture of the system - std::string arch; -#ifdef __aarch64__ - arch = "arm64"; -#elif __x86_64__ - arch = "amd64"; -#endif - return arch; - } - - // ------------------------------------------------------------------------------------------------ - // cRemoteTempFolder : SHARED CLASS - // ------------------------------------------------------------------------------------------------ - 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); - std::string mkdir_cmd = "mkdir -p " + quote(p); - if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) - error << "Failed to create temp directory on server" << std::endl; - else - mPath = p; - } - - cRemoteTempFolder::~cRemoteTempFolder() - { - std::string rm_cmd = "rm -rf " + quote(mPath); - execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent); - } - - std::string cRemoteTempFolder::path() const - { - return mPath; - } - - // ------------------------------------------------------------------------------------------------ - // get_all_services_status : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - std::map get_all_services_status(std::string server_name) - { - std::map status; - - server_env_manager env(server_name); - if (!env.is_valid()) - { - error << "Invalid server environment" << std::endl; - return status; - } - - std::string output; - if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), - "./_allservicesstatus.sh", - {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}), - cMode::CaptureOutput | cMode::Silent, - &output)) - return status; - - std::stringstream ss(output); - std::string line; - while (std::getline(ss, line)) - { - std::string key, value; - std::size_t pos = line.find("="); - if (pos != std::string::npos) - { - key = dequote(trim(line.substr(0, pos))); - value = dequote(trim(line.substr(pos + 1))); - - // decode key, it's of format SERVICENAME_[HEALTH|PORTS] - std::string service_name = key.substr(0, key.find_last_of("_")); - std::string status_type = key.substr(key.find_last_of("_") + 1); - - if (status_type == "HEALTH") - { // healthy|unhealthy|unknown - if (value == "healthy") - status[service_name].health = HealthStatus::HEALTHY; - else if (value == "unhealthy") - status[service_name].health = HealthStatus::UNHEALTHY; - else if (value == "unknown") - status[service_name].health = HealthStatus::UNKNOWN; - else - status[service_name].health = HealthStatus::ERROR; + if (ctx.args.size() == 0) + { // just the command, no args yet. + // list servers + std::vector servers = get_configured_servers(); + for (const auto &server : servers) + { + rawout << server.name << std::endl; } - else if (status_type == "PORTS") - { // port1,port2,port3 - std::vector ports = string2multi(value); - for (const auto &port : ports) - { - if (port != "unknown") - status[service_name].ports.push_back(str2int(port)); + } + else if (ctx.args.size() == 1) + { + // list services + std::vector services = get_server_services_info(ctx.args[0]); + for (const auto &service : services) + { + rawout << service.service_name << std::endl; + } + } + } + + // ------------------------------------------------------------------------------------------------ + // std_autocomplete_allowall : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + void std_autocomplete_allowall(const CommandContext &ctx) + { + std_autocomplete(ctx); + if (ctx.args.size() == 1) + rawout << "all" << std::endl; + } + + // ------------------------------------------------------------------------------------------------ + // rsync_tree_to_remote : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + bool rsync_tree_to_remote( + const std::string &local_path, + const std::string &remote_path, + server_env_manager &server_env, + bool silent) + { + ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync."); + + std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " + + quote(local_path + "/") + " " + + quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + + remote_path + "/"); + return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); + } + + // ------------------------------------------------------------------------------------------------ + // get_arch : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string get_arch() + { + // determine the architecture of the system + std::string arch; +#ifdef __aarch64__ + arch = "arm64"; +#elif __x86_64__ + arch = "amd64"; +#endif + return arch; + } + + // ------------------------------------------------------------------------------------------------ + // cRemoteTempFolder : SHARED CLASS + // ------------------------------------------------------------------------------------------------ + 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); + std::string mkdir_cmd = "mkdir -p " + quote(p); + if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) + error << "Failed to create temp directory on server" << std::endl; + else + mPath = p; + } + + cRemoteTempFolder::~cRemoteTempFolder() + { + std::string rm_cmd = "rm -rf " + quote(mPath); + execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent); + } + + std::string cRemoteTempFolder::path() const + { + return mPath; + } + + // ------------------------------------------------------------------------------------------------ + // get_all_services_status : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::map get_all_services_status(std::string server_name) + { + std::map status; + + server_env_manager env(server_name); + if (!env.is_valid()) + { + error << "Invalid server environment" << std::endl; + return status; + } + + std::string output; + if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}), + cMode::CaptureOutput | cMode::Silent, + &output)) + return status; + + std::stringstream ss(output); + std::string line; + while (std::getline(ss, line)) + { + std::string key, value; + std::size_t pos = line.find("="); + if (pos != std::string::npos) + { + key = dequote(trim(line.substr(0, pos))); + value = dequote(trim(line.substr(pos + 1))); + + // decode key, it's of format SERVICENAME_[HEALTH|PORTS] + std::string service_name = key.substr(0, key.find_last_of("_")); + std::string status_type = key.substr(key.find_last_of("_") + 1); + + if (status_type == "HEALTH") + { // healthy|unhealthy|unknown + if (value == "healthy") + status[service_name].health = HealthStatus::HEALTHY; + else if (value == "unhealthy") + status[service_name].health = HealthStatus::UNHEALTHY; + else if (value == "unknown") + status[service_name].health = HealthStatus::UNKNOWN; + else + status[service_name].health = HealthStatus::ERROR; + } + else if (status_type == "PORTS") + { // port1,port2,port3 + std::vector ports = string2multi(value); + for (const auto &port : ports) + { + if (port != "unknown") + status[service_name].ports.push_back(str2int(port)); + } } } } + return status; } - return status; - } - - - // ------------------------------------------------------------------------------------------------ - // healthtick : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - std::string healthtick(const std::string &server, const std::string &service) - { - std::string green_tick = "\033[32m✓\033[0m"; - std::string red_cross = "\033[31m✗\033[0m"; - std::string yellow_exclamation = "\033[33m!\033[0m"; - std::string unknown = "\033[37m✓\033[0m"; - - HealthStatus status = is_healthy(server, service); - if (status == HealthStatus::HEALTHY) - return green_tick; - else if (status == HealthStatus::UNHEALTHY) - return red_cross; - else if (status == HealthStatus::UNKNOWN) - return unknown; - else - return yellow_exclamation; - } - - // ------------------------------------------------------------------------------------------------ - // HealthStatus2String : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - std::string HealthStatus2String(HealthStatus status) - { - if (status == HealthStatus::HEALTHY) - return ":tick:"; - else if (status == HealthStatus::UNHEALTHY) - return ":cross:"; - else if (status == HealthStatus::UNKNOWN) - return ":greytick:"; - else if (status == HealthStatus::NOTINSTALLED) - return ":warning:"; - else - return ":error:"; - } - - - // ------------------------------------------------------------------------------------------------ - // is_healthy : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - HealthStatus is_healthy(const std::string &server, const std::string &service) - { - server_env_manager env(server); - if (!env.is_valid()) + // ------------------------------------------------------------------------------------------------ + // healthtick : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string healthtick(const std::string &server, const std::string &service) { - error << "Server service not initialized" << std::endl; - return HealthStatus::ERROR; + std::string green_tick = "\033[32m✓\033[0m"; + std::string red_cross = "\033[31m✗\033[0m"; + std::string yellow_exclamation = "\033[33m!\033[0m"; + std::string unknown = "\033[37m✓\033[0m"; + + HealthStatus status = is_healthy(server, service); + if (status == HealthStatus::HEALTHY) + return green_tick; + else if (status == HealthStatus::UNHEALTHY) + return red_cross; + else if (status == HealthStatus::UNKNOWN) + return unknown; + else + return yellow_exclamation; } - if (!env.check_remote_dir_exists(remotepath::service(server, service))) + // ------------------------------------------------------------------------------------------------ + // HealthStatus2String : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string HealthStatus2String(HealthStatus status) { - return HealthStatus::NOTINSTALLED; + if (status == HealthStatus::HEALTHY) + return ":tick:"; + else if (status == HealthStatus::UNHEALTHY) + return ":cross:"; + else if (status == HealthStatus::UNKNOWN) + return ":greytick:"; + else if (status == HealthStatus::NOTINSTALLED) + return ":warning:"; + else + return ":error:"; } - std::string script_path = remotepath::service_template(server, service) + "/status.sh"; - if (!env.check_remote_file_exists(script_path)) + // ------------------------------------------------------------------------------------------------ + // is_healthy : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + HealthStatus is_healthy(const std::string &server, const std::string &service) { - return HealthStatus::UNKNOWN; + server_env_manager env(server); + if (!env.is_valid()) + { + error << "Server service not initialized" << std::endl; + return HealthStatus::ERROR; + } + + if (!env.check_remote_dir_exists(remotepath::service(server, service))) + { + return HealthStatus::NOTINSTALLED; + } + + std::string script_path = remotepath::service_template(server, service) + "/status.sh"; + if (!env.check_remote_file_exists(script_path)) + { + return HealthStatus::UNKNOWN; + } + + // Run status script, does not display output. + if (!env.run_remote_template_command(service, "status", {}, true, {})) + return HealthStatus::UNHEALTHY; + return HealthStatus::HEALTHY; } - // Run status script, does not display output. - if (!env.run_remote_template_command(service, "status", {}, true, {})) - return HealthStatus::UNHEALTHY; - return HealthStatus::HEALTHY; - } + // ------------------------------------------------------------------------------------------------ + // healthmark : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string healthmark(const std::string &server, const std::string &service) + { + HealthStatus status = is_healthy(server, service); + return HealthStatus2String(status); + } + // ------------------------------------------------------------------------------------------------ + // cBackupFileName : SHARED CLASS + // ------------------------------------------------------------------------------------------------ + cBackupFileName::cBackupFileName(const std::string &server, const std::string &service, const std::string &template_name) + { + mServer = server; + mService = service; + mTemplateName = template_name; + // Get current datetime for backup filename + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::system_clock::to_time_t(now); + std::stringstream datetime; + datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S"); + mDatetime = datetime.str(); + } - // ------------------------------------------------------------------------------------------------ - // healthmark : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - std::string healthmark(const std::string &server, const std::string &service) - { - HealthStatus status = is_healthy(server, service); - return HealthStatus2String(status); - } + cBackupFileName::cBackupFileName(const std::string &filename) + { + // Parse the filename according to the format: + // server + magic_string() + template_name + magic_string() + service + magic_string() + datetime + ".tgz" + std::string name = filename; + if (name.size() > 4 && name.substr(name.size() - 4) == ".tgz") + name = name.substr(0, name.size() - 4); + std::string sep = magic_string(); + size_t first = name.find(sep); + size_t second = name.find(sep, first + sep.size()); + size_t third = name.find(sep, second + sep.size()); + if (first == std::string::npos || second == std::string::npos || third == std::string::npos) + { + mServer = mService = mTemplateName = mDatetime = ""; + return; + } + mServer = name.substr(0, first); + mTemplateName = name.substr(first + sep.size(), second - (first + sep.size())); + mService = name.substr(second + sep.size(), third - (second + sep.size())); + mDatetime = name.substr(third + sep.size()); + } + + std::string cBackupFileName::get_filename() const + { + return mServer + magic_string() + mTemplateName + magic_string() + mService + magic_string() + mDatetime + ".tgz"; + } + + std::string cBackupFileName::get_server() const { return mServer; } + std::string cBackupFileName::get_service() const { return mService; } + std::string cBackupFileName::get_template_name() const { return mTemplateName; } + std::string cBackupFileName::get_datetime() const { return mDatetime; } + + bool cBackupFileName::is_valid() const + { + // All fields must be non-empty, and none may contain the magic string + return !mServer.empty() && !mService.empty() && !mTemplateName.empty() && !mDatetime.empty() && + !has_magic_string(mServer) && !has_magic_string(mService) && !has_magic_string(mTemplateName); + } + + // ------------------------------------------------------------------------------------------------ + // scp_file_to_remote : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + bool scp_file_to_remote(const server_env_manager &server_env, const std::string &local_path, const std::string &remote_path, bool silent) + { + if (!server_env.is_valid()) + { + error << "Invalid server environment" << std::endl; + return false; + } + ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp."); + std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + quote(local_path) + " " + server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : ""); + return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); + } + + // ------------------------------------------------------------------------------------------------ + // scp_file_from_remote : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + bool scp_file_from_remote(const server_env_manager &server_env, const std::string &remote_path, const std::string &local_path, bool silent) + { + if (!server_env.is_valid()) + { + error << "Invalid server environment" << std::endl; + return false; + } + ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp."); + std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : ""); + return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); + } } // namespace shared_commands diff --git a/source/src/commands/shared_commands.hpp b/source/src/commands/shared_commands.hpp index ad3ebe3..ce21123 100644 --- a/source/src/commands/shared_commands.hpp +++ b/source/src/commands/shared_commands.hpp @@ -57,6 +57,29 @@ namespace dropshell void std_autocomplete(const CommandContext &ctx); void std_autocomplete_allowall(const CommandContext &ctx); + class cBackupFileName + { + public: + cBackupFileName(const std::string &server, const std::string &service, const std::string &template_name); + cBackupFileName(const std::string &filename); + + std::string get_filename() const; + std::string get_server() const; + std::string get_service() const; + std::string get_template_name() const; + std::string get_datetime() const; + + bool is_valid() const; + + private: + std::string mServer; + std::string mService; + std::string mTemplateName; + std::string mDatetime; + }; + + bool scp_file_to_remote(const server_env_manager &server_env, const std::string &local_path, const std::string &remote_path, bool silent); + bool scp_file_from_remote(const server_env_manager &server_env, const std::string &remote_path, const std::string &local_path, bool silent); } // namespace shared_commands } // namespace dropshell diff --git a/source/src/service_runner.cpp b/source/src/service_runner.cpp index 1a43d10..2660b8f 100644 --- a/source/src/service_runner.cpp +++ b/source/src/service_runner.cpp @@ -91,7 +91,6 @@ namespace fs = std::filesystem; namespace dropshell { -static const std::string magic_string = "-_-"; service_runner::service_runner(const std::string& server_name, const std::string& service_name) : mServerEnv(server_name), mServer(server_name), mService(service_name), mValid(false) @@ -298,7 +297,7 @@ bool service_runner::restore(std::string backup_file, bool silent) } // split the backup filename into parts based on the magic string - std::vector parts = dropshell::split(backup_file, magic_string); + std::vector parts = dropshell::split(backup_file, "-_-"); if (parts.size() != 4) { std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl; return false; @@ -383,12 +382,6 @@ bool service_runner::restore(std::string backup_file, bool silent) } -bool name_breaks_backups(std::string name) -{ - // if name contains -_-, return true - return name.find("-_-") != std::string::npos; -} - // backup the service over ssh, using the credentials from server.env (via server_env.hpp) // 1. run backup.sh on the server // 2. create a backup file with format server-service-datetime.tgz @@ -452,17 +445,18 @@ bool service_runner::backup(bool silent) { std::stringstream datetime; datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S"); - if (name_breaks_backups(mServer)) {std::cerr << "Error: Server name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;} - if (name_breaks_backups(mService)) {std::cerr << "Error: Service name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;} - if (name_breaks_backups(service_info.template_name)) {std::cerr << "Error: Service template name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;} - // Construct backup filename - std::string backup_filename = mServer + magic_string + service_info.template_name + magic_string + mService + magic_string + datetime.str() + ".tgz"; + shared_commands::cBackupFileName backup_filename_construction(mServer, mService, service_info.template_name); + if (!backup_filename_construction.is_valid()) { + std::cerr << "Invalid backup filename" << std::endl; + return false; + } + std::string backup_filename = backup_filename_construction.get_filename(); std::string remote_backup_file_path = remote_backups_dir + "/" + backup_filename; std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string(); // assert that the backup filename is valid - -_- appears exactly 3 times in local_backup_file_path. - ASSERT(3 == count_substring(magic_string, local_backup_file_path), "Invalid backup filename"); + ASSERT(3 == count_substring("-_-", local_backup_file_path), "Invalid backup filename"); { // Run backup script shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv); @@ -501,7 +495,7 @@ std::string service_runner::get_latest_backup_file(const std::string& server, co } // Build the expected prefix for backup files - std::string prefix = server + magic_string + info.template_name + magic_string + service + magic_string; + std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-"; std::string latest_file; std::string latest_datetime; diff --git a/source/src/utils/utils.cpp b/source/src/utils/utils.cpp index 2a10e9b..c75f5da 100644 --- a/source/src/utils/utils.cpp +++ b/source/src/utils/utils.cpp @@ -10,6 +10,16 @@ namespace dropshell { +std::string magic_string() { + return "-_-"; +} + +bool has_magic_string(std::string name) +{ + return name.find(magic_string()) != std::string::npos; +} + + void maketitle(const std::string& title, sColour colour) { colourstream(colour) << std::string(title.length() + 4, '-') << std::endl; colourstream(colour) << "| " << title << " |" << std::endl; diff --git a/source/src/utils/utils.hpp b/source/src/utils/utils.hpp index 4655238..f9a57dc 100644 --- a/source/src/utils/utils.hpp +++ b/source/src/utils/utils.hpp @@ -16,6 +16,8 @@ void maketitle(const std::string& title, sColour colour=sColour::INFO); bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line); +std::string magic_string(); +bool has_magic_string(std::string name); // utility functions std::string trim(std::string str);