From e033489f9bcc9fa71eaa86d49d07ce28daf3a7b2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 26 Apr 2025 21:06:42 +1200 Subject: [PATCH] Refactoring backups. --- src/main.cpp | 16 ++++ src/main_commands.cpp | 156 ++++++++++++++++++++++++++++++++++++++ src/main_commands.hpp | 3 +- src/service_runner.cpp | 77 ------------------- src/service_runner.hpp | 25 ++++-- src/services.cpp | 6 +- src/services.hpp | 2 +- src/utils/directories.cpp | 14 ++-- src/utils/directories.hpp | 2 +- src/utils/utils.cpp | 90 ++++++++++++++++++++++ src/utils/utils.hpp | 4 + 11 files changed, 298 insertions(+), 97 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cdbbed3..d18f618 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -181,6 +181,22 @@ int main(int argc, char* argv[]) { return 0; } + if (cmd == "backup" || cmd=="backups") { + if (argc < 4) { + std::cerr << "Error: backup requires a target server and target service to back up" << std::endl; + return 1; + } + return dropshell::main_commands::backup(argvec); + } + + if (cmd == "restore") { + if (argc < 4) { + std::cerr << "Error: restore requires a target server, target service the backup file to restore" << std::endl; + return 1; + } + return dropshell::main_commands::restore(argvec); + } + // handle running a command. std::set commands; dropshell::get_all_used_commands(commands); diff --git a/src/main_commands.cpp b/src/main_commands.cpp index 4713112..ab4e287 100644 --- a/src/main_commands.cpp +++ b/src/main_commands.cpp @@ -3,8 +3,12 @@ #include "utils/directories.hpp" #include "utils/utils.hpp" #include "utils/readmes.hpp" +#include "service_runner.hpp" #include "config.hpp" +#include "templates.hpp" #include +#include +#include namespace dropshell { @@ -44,6 +48,158 @@ int init(const std::vector &args) } } +int restore(const std::vector &args) +{ + if (args.size() < 3) { + std::cerr << "Error: restore command requires a directory argument" << std::endl; + return 1; + } + + std::string server_name = args[2]; + std::string service_name = args[3]; + std::string backup_file = args[4]; + + + ServiceInfo service_info = get_service_info(server_name, service_name); + if (service_info.path.empty()) { + std::cerr << "Error: Service not found" << std::endl; + return 1; + } + + if (! std::filesystem::exists(backup_file)) { + std::cerr << "Error: Backup file not found" << std::endl; + return 1; + } + // backup_file is in format: server_name-template_name-service_name-YYYY-MM-DD_HH-MM-SS.tgz + // count '-' in backup_file + int dash_count = std::count(backup_file.begin(), backup_file.end(), '-'); + if (dash_count != 3) { + std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl; + return 1; + } + + + + // if (service_info.template_name != backup_template_name) { + // std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl; + // return 1; + // } + + // std::string restore_cmd = "tar -xzvf " + backup_file + " -C " + service_info.path; + // std::string restore_cmd = "tar -xzvf " + backup_file + " -C " + service_info.path; + + + + return 0; +} + +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 + // 3. store it in the server's DROPSHELL_DIR/backups folder + // 4. copy it to the local user_dir/backups folder + +// ------------------------------------------------------------------------------------------------ +// Backup the service. +// ------------------------------------------------------------------------------------------------ +int backup(const std::vector & args) { + std::string server_name = args[2]; + std::string service_name = args[3]; + + service_runner runner; + if (!runner.init(server_name, service_name)) + { + std::cerr << "Error: Failed to initialise service runner" << std::endl; + return 1; + } + + server_env env(server_name); + if (!env.is_valid()) { + std::cerr << "Error: Invalid server environment" << std::endl; + return 1; + } + + std::string command = "backup"; + + std::string script_path = runner.sr_get_local_service_template_path() + "/" + command + ".sh"; + if (!template_command_exists(runner.sr_get_service_template_name(), command)) { + std::cout << "No backup script for " << runner.sr_get_service_template_name() << std::endl; + return true; // nothing to back up. + } + + // Check if basic installed stuff is in place. + if (!runner.check_remote_items_exist({runner.sr_get_remote_service_path(),script_path,runner.sr_get_remote_service_env_file()})) + { + std::cerr << "Error: Required service directories not found on remote server" << std::endl; + std::cerr << "Is the service installed?" << std::endl; + return false; + } + + // Create backups directory on server if it doesn't exist + std::string remote_backups_dir = get_remote_backups_path(server_name); + std::cout << "Remote backups directory on "<< server_name <<": " << remote_backups_dir << std::endl; + std::string mkdir_cmd = "'mkdir -p " + quote(remote_backups_dir) + "'"; + if (!runner.execute_ssh_command(mkdir_cmd, "Failed to create backups directory on server")) { + return false; + } + + // Create backups directory locally if it doesn't exist + std::string local_backups_dir = get_local_backup_path(); + if (local_backups_dir.empty()) { + std::cerr << "Error: Local backups directory not found - is DropShell initialised?" << std::endl; + return false; + } + if (!std::filesystem::exists(local_backups_dir)) + std::filesystem::create_directories(local_backups_dir); + + // 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"); + + if (name_breaks_backups(server_name)) {std::cerr << "Error: Server name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;} + if (name_breaks_backups(service_name)) {std::cerr << "Error: Service name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;} + if (name_breaks_backups(runner.sr_get_service_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 = server_name + "-_-" + runner.sr_get_service_template_name() + "-_-" + service_name + "-_-" + datetime.str() + ".tgz"; + 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("-_-", local_backup_file_path)); + + // Run backup script + std::string backup_cmd = "'cd " + quote(runner.sr_get_remote_service_template_path()) + + " && /bin/bash "+quote(script_path)+" "+quote(runner.sr_get_remote_service_config_path())+" "+ + quote(remote_backup_file_path)+"'"; + + if (!runner.execute_ssh_command(backup_cmd, "Backup script failed")) { + std::cerr << "Backup script failed: " << backup_cmd << std::endl; + return false; + } + + // Copy backup file from server to local + std::string scp_cmd = "scp -P " + runner.sr_get_server_env().get_SSH_PORT() + " " + + runner.sr_get_server_env().get_SSH_USER() + "@" + runner.sr_get_server_env().get_SSH_HOST() + ":" + + quote(remote_backup_file_path) + " " + quote(local_backup_file_path); + if (!runner.execute_local_command(scp_cmd, "Failed to copy backup file from server")) { + return false; + } + + std::cout << "Backup created successfully: " << local_backup_file_path << std::endl; + return true; +} + + } // namespace main_commands } // namespace dropshell diff --git a/src/main_commands.hpp b/src/main_commands.hpp index 4c0cfca..9a5994a 100644 --- a/src/main_commands.hpp +++ b/src/main_commands.hpp @@ -9,7 +9,8 @@ namespace dropshell { namespace main_commands { int init(const std::vector &args); - + int restore(const std::vector &args); + int backup(const std::vector &args); } // namespace main_commands } // namespace dropshell diff --git a/src/service_runner.cpp b/src/service_runner.cpp index c7008fc..0a6ebc8 100644 --- a/src/service_runner.cpp +++ b/src/service_runner.cpp @@ -279,8 +279,6 @@ bool service_runner::run_command(const std::string& command) { if (command == "uninstall") return uninstall(); - if (command == "backup") - return backup(); if (command == "ssh") { interactive_ssh_service(); return true; @@ -292,81 +290,6 @@ bool service_runner::run_command(const std::string& command) { } - -// ------------------------------------------------------------------------------------------------ -// Backup the service. -// ------------------------------------------------------------------------------------------------ -bool service_runner::backup() { - maketitle("Backing up " + m_service_info.service_name + " (" + m_service_info.template_name + ") on " + m_server_name); - - if (!m_server_env) return false; // should never hit this. - - std::string command = "backup"; - - std::string script_path = mRemote_service_template_path + "/" + command + ".sh"; - if (!template_command_exists(m_service_info.template_name, command)) { - std::cout << "No backup script for " << m_service_info.template_name << std::endl; - return true; // nothing to back up. - } - - // Check if basic installed stuff is in place. - if (!check_remote_items_exist({mRemote_service_path,script_path,mRemote_service_env_file})) - { - std::cerr << "Error: Required service directories not found on remote server" << std::endl; - std::cerr << "Is the service installed?" << std::endl; - return false; - } - - // Create backups directory on server if it doesn't exist - std::string server_backups_dir = m_server_env->get_DROPSHELL_DIR() + "/backups"; - std::string mkdir_cmd = "'mkdir -p " + quote(server_backups_dir) + "'"; - if (!execute_ssh_command(mkdir_cmd, "Failed to create backups directory on server")) { - return false; - } - - // Create backups directory locally if it doesn't exist - std::string local_backups_dir = get_local_backup_path(); - if (local_backups_dir.empty()) { - std::cerr << "Error: Local backups directory not found - is DropShell initialised?" << std::endl; - return false; - } - if (!fs::exists(local_backups_dir)) { - fs::create_directories(local_backups_dir); - } - - // 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"); - - // Construct backup filename - std::string backup_filename = m_server_name + "-" + m_service_info.service_name + "-" + datetime.str() + ".tgz"; - std::string server_backup_path = server_backups_dir + "/" + backup_filename; - std::string local_backup_path = (fs::path(local_backups_dir) / backup_filename).string(); - - // Run backup script - std::string backup_cmd = "'cd " + quote(mRemote_service_template_path) + - " && /bin/bash "+quote(script_path)+" "+quote(mRemote_service_config_path)+" "+ - quote(server_backup_path)+"'"; - - if (!execute_ssh_command(backup_cmd, "Backup script failed")) { - std::cerr << "Backup script failed: " << backup_cmd << std::endl; - return false; - } - - // Copy backup file from server to local - std::string scp_cmd = "scp -P " + m_server_env->get_SSH_PORT() + " " + - m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + - quote(server_backup_path) + " " + quote(local_backup_path); - if (!execute_local_command(scp_cmd, "Failed to copy backup file from server")) { - return false; - } - - std::cout << "Backup created successfully: " << local_backup_path << std::endl; - return true; -} - std::map service_runner::get_all_services_status(std::string server_name) { std::map status; diff --git a/src/service_runner.hpp b/src/service_runner.hpp index cbb2365..48d13a3 100644 --- a/src/service_runner.hpp +++ b/src/service_runner.hpp @@ -11,6 +11,8 @@ #include #include "server_env.hpp" #include "services.hpp" +#include "utils/utils.hpp" +#include "utils/assert.hpp" namespace dropshell { @@ -73,13 +75,6 @@ class service_runner { // 4. remove the service directory from the server bool uninstall(); - // 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 - // 3. store it in the server's DROPSHELL_DIR/backups folder - // 4. copy it to the local user_dir/backups folder - bool backup(); - // restore the service over ssh, using the credentials from server.env (via server_env.hpp) // 1. copy the backup file to the server's DROPSHELL_DIR/backups folder // 2. run the restore.sh script on the server, passing the {service_name}.env file as an argument @@ -102,7 +97,23 @@ class service_runner { std::string mRemote_service_template_path; std::string mRemote_service_env_file; + public: + std::string sr_get_server_name() const { return m_server_name; } + std::string sr_get_service_name() const { return m_service_info.service_name; } + std::string sr_get_service_path() const { return m_service_info.path; } + std::string sr_get_service_template_name() const { return m_service_info.template_name; } + std::string sr_get_local_service_template_path() const { return m_service_info.template_local_path; } + + ServiceInfo sr_get_service_info() const { return m_service_info; } + const server_env& sr_get_server_env() const { ASSERT(m_server_env); return *m_server_env; } + + const std::string& sr_get_remote_service_path() const { return mRemote_service_path; } + const std::string& sr_get_remote_service_config_path() const { return mRemote_service_config_path; } + const std::string& sr_get_remote_service_template_path() const { return mRemote_service_template_path; } + const std::string& sr_get_remote_service_env_file() const { return mRemote_service_env_file; } + // Helper methods + public: static std::string construct_ssh_cmd(const server_env &env); std::string construct_ssh_cmd() const; diff --git a/src/services.cpp b/src/services.cpp index a1d97a7..79fed98 100644 --- a/src/services.cpp +++ b/src/services.cpp @@ -83,7 +83,7 @@ ServiceInfo get_service_info(const std::string &server_name, const std::string & } // find the template path - service.template_path = tinfo.path; + service.template_local_path = tinfo.path; return service; } @@ -96,14 +96,14 @@ std::set get_used_commands(const std::string &server_name, const st return commands; ServiceInfo service = get_service_info(server_name, service_name); - if (service.template_path.empty()) { + if (service.template_local_path.empty()) { std::cerr << "Error: Service not found: " << service_name << std::endl; return commands; } // iterate over all files in the template path, and add the command name to the set. // commands are .sh files that don't begin with _ - fs::path template_path = fs::path(service.template_path); + fs::path template_path = fs::path(service.template_local_path); for (const auto& entry : fs::directory_iterator(template_path)) { if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0)) commands.insert(entry.path().stem().string()); diff --git a/src/services.hpp b/src/services.hpp index 5bdf7fa..f3806fd 100644 --- a/src/services.hpp +++ b/src/services.hpp @@ -11,7 +11,7 @@ namespace dropshell { std::string path; std::string service_name; std::string template_name; - std::string template_path; + std::string template_local_path; }; std::vector get_server_services_info(const std::string& server_name); diff --git a/src/utils/directories.cpp b/src/utils/directories.cpp index d2605dc..6639d8d 100644 --- a/src/utils/directories.cpp +++ b/src/utils/directories.cpp @@ -3,9 +3,9 @@ #include "server_env.hpp" #include #include -#include +#include -namespace fs = boost::filesystem; +namespace fs = std::filesystem; namespace dropshell { @@ -187,14 +187,14 @@ std::string get_local_service_env_path(const std::string &server_name, const std return (fs::path(service_path) / "template").string(); } - std::string get_remote_service_backups_path(const std::string &server_name, const std::string &service_name) + std::string get_remote_backups_path(const std::string &server_name) { - if (server_name.empty() || service_name.empty()) + if (server_name.empty()) return std::string(); - std::string service_path = get_remote_service_path(server_name, service_name); - if (service_path.empty()) + std::string dropshell_path = get_remote_DROPSHELL_path(server_name); + if (dropshell_path.empty()) return std::string(); - return (fs::path(service_path) / "backups").string(); + return (fs::path(dropshell_path) / "backups").string(); } std::string get_remote_service_env_file(const std::string &server_name, const std::string &service_name) diff --git a/src/utils/directories.hpp b/src/utils/directories.hpp index e2d2afa..230931b 100644 --- a/src/utils/directories.hpp +++ b/src/utils/directories.hpp @@ -57,7 +57,7 @@ namespace dropshell { std::string get_remote_service_path(const std::string &server_name, const std::string &service_name); std::string get_remote_service_config_path(const std::string &server_name, const std::string &service_name); std::string get_remote_service_template_path(const std::string &server_name, const std::string &service_name); - std::string get_remote_service_backups_path(const std::string &server_name, const std::string &service_name); + std::string get_remote_backups_path(const std::string &server_name); std::string get_remote_service_env_file(const std::string &server_name, const std::string &service_name); } // namespace dropshell diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index f89afdc..03c9808 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -157,4 +157,94 @@ void ensure_directories_exist(std::vector directories) } } + +//https://www.geeksforgeeks.org/kmp-algorithm-for-pattern-searching/ +void constructLps(const std::string &pat, std::vector &lps) { + + // len stores the length of longest prefix which + // is also a suffix for the previous index + int len = 0; + + // lps[0] is always 0 + lps[0] = 0; + + int i = 1; + while (i < pat.length()) { + + // If characters match, increment the size of lps + if (pat[i] == pat[len]) { + len++; + lps[i] = len; + i++; + } + + // If there is a mismatch + else { + if (len != 0) { + + // Update len to the previous lps value + // to avoid reduntant comparisons + len = lps[len - 1]; + } + else { + + // If no matching prefix found, set lps[i] to 0 + lps[i] = 0; + i++; + } + } + } +} + +std::vector search(const std::string &pat, const std::string &txt) { + int n = txt.length(); + int m = pat.length(); + + std::vector lps(m); + std::vector res; + + constructLps(pat, lps); + + // Pointers i and j, for traversing + // the text and pattern + int i = 0; + int j = 0; + + while (i < n) { + + // If characters match, move both pointers forward + if (txt[i] == pat[j]) { + i++; + j++; + + // If the entire pattern is matched + // store the start index in result + if (j == m) { + res.push_back(i - j); + + // Use LPS of previous index to + // skip unnecessary comparisons + j = lps[j - 1]; + } + } + + // If there is a mismatch + else { + + // Use lps value of previous index + // to avoid redundant comparisons + if (j != 0) + j = lps[j - 1]; + else + i++; + } + } + return res; +} + +int count_substring(const std::string &substring, const std::string &text) { + std::vector positions = search(substring, text); + return positions.size(); +} + } // namespace dropshell \ No newline at end of file diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index 778a8df..00334e2 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -28,4 +28,8 @@ void recursive_copy(const std::string & source, const std::string & destination) void ensure_directories_exist(std::vector directories); +// KMP algorithm +std::vector search(const std::string &pat, const std::string &txt); +int count_substring(const std::string &substring, const std::string &text); + } // namespace dropshell \ No newline at end of file