Integrate in agent.

This commit is contained in:
Your Name 2025-04-25 20:06:47 +12:00
parent 513431b7a3
commit 9409adf4f6
10 changed files with 306 additions and 130 deletions

View File

@ -6,6 +6,7 @@
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "services.hpp" #include "services.hpp"
#include "config.hpp" #include "config.hpp"
#include "templates.hpp"
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
@ -58,28 +59,19 @@ void list_servers() {
tp.add_row({"Name", "Address", "Health", "Ports"}); tp.add_row({"Name", "Address", "Health", "Ports"});
std::for_each(std::execution::par, servers.begin(), servers.end(), [&](const ServerInfo& server) { std::for_each(std::execution::par, servers.begin(), servers.end(), [&](const ServerInfo& server) {
std::vector<int> ports_used;
std::string serviceticks = "";
std::vector<ServiceInfo> services = get_server_services_info(server.name);
std::for_each(std::execution::par, services.begin(), services.end(), [&](const ServiceInfo& service) { std::map<std::string, ServiceStatus> status = service_runner::get_all_services_status(server.name);
service_runner ss;
if (ss.init(server.name, service.service_name)) std::set<int> ports_used;
serviceticks += ss.healthmark() + " "; std::string serviceticks = "";
else std::cout<<"Error: Failed to initialise service runner for server: ["<<server.name<<"] and service: ["<<service.service_name<<"]"<<std::endl; for (const auto& [service_name, service_status] : status) {
std::vector<int> ports = ss.get_ports(); ports_used.insert(service_status.ports.begin(), service_status.ports.end());
ports_used.insert(ports_used.end(), ports.begin(), ports.end()); serviceticks += service_runner::HealthStatus2String(service_status.health) + " ";
}); }
// convert ports_used to string
std::string ports_used_str = ""; std::string ports_used_str = "";
bool first = true; for (const auto& port : ports_used)
for (const auto& port : ports_used) { ports_used_str += std::to_string(port) + " ";
if (!first) {
ports_used_str += ", ";
}
ports_used_str += std::to_string(port);
first = false;
}
tp.add_row({server.name, server.ssh_host, serviceticks, ports_used_str}); tp.add_row({server.name, server.ssh_host, serviceticks, ports_used_str});
}); });
tp.print(); tp.print();
@ -161,5 +153,4 @@ void show_server_details(const std::string& server_name) {
} // end of list services } // end of list services
} // end of show_server_details } // end of show_server_details
} // namespace dropshell } // namespace dropshell

View File

@ -4,6 +4,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "service_runner.hpp" // for ServiceStatus
namespace dropshell { namespace dropshell {
// Server information structure // Server information structure

View File

@ -49,15 +49,23 @@ bool service_runner::init(const std::string& server_name, const std::string& ser
} }
// Helper method implementations // Helper method implementations
std::string service_runner::construct_ssh_cmd() const { std::string service_runner::construct_ssh_cmd(const server_env &env) {
std::stringstream ssh_cmd; std::stringstream ssh_cmd;
ssh_cmd << "ssh -p " << m_server_env->get_SSH_PORT() << " " ssh_cmd << "ssh -p " << env.get_SSH_PORT() << " "
<< m_server_env->get_SSH_USER() << "@" << m_server_env->get_SSH_HOST() << " "; << env.get_SSH_USER() << "@" << env.get_SSH_HOST() << " ";
return ssh_cmd.str(); return ssh_cmd.str();
} }
bool service_runner::check_remote_dir_exists(const std::string& dir_path) const { std::string service_runner::construct_ssh_cmd() const
std::string check_dir_cmd = construct_ssh_cmd() + "'test -d " + dir_path + "'"; {
if (!m_server_env)
return std::string();
return construct_ssh_cmd(*m_server_env);
}
bool service_runner::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) { if (system(check_dir_cmd.c_str()) != 0) {
std::cerr << "Error: Directory not found on remote server:" << fs::path(dir_path).filename().string() << std::endl; std::cerr << "Error: Directory not found on remote server:" << fs::path(dir_path).filename().string() << std::endl;
return false; return false;
@ -66,7 +74,7 @@ bool service_runner::check_remote_dir_exists(const std::string& dir_path) const
} }
bool service_runner::check_remote_file_exists(const std::string& file_path) const { bool service_runner::check_remote_file_exists(const std::string& file_path) const {
std::string check_cmd = construct_ssh_cmd() + "'test -f " + file_path + "'"; std::string check_cmd = construct_ssh_cmd() + "'test -f " + quote(file_path) + "'";
if (system(check_cmd.c_str()) != 0) { if (system(check_cmd.c_str()) != 0) {
std::cerr << "Error: File not found on remote server: " << fs::path(file_path).filename().string() << std::endl; std::cerr << "Error: File not found on remote server: " << fs::path(file_path).filename().string() << std::endl;
return false; return false;
@ -80,7 +88,7 @@ bool service_runner::check_remote_items_exist(const std::vector<std::string> &fi
std::string file_paths_str; std::string file_paths_str;
std::string file_names_str; std::string file_names_str;
for (const auto& file_path : file_paths) { for (const auto& file_path : file_paths) {
file_paths_str += file_path + " "; file_paths_str += quote(file_path) + " ";
file_names_str += fs::path(file_path).filename().string() + " "; file_names_str += fs::path(file_path).filename().string() + " ";
} }
// check if all items in the vector exist on the remote server, in a single command. // check if all items in the vector exist on the remote server, in a single command.
@ -104,6 +112,22 @@ bool service_runner::execute_local_command(const std::string& command, const std
return okay; return okay;
} }
bool service_runner::execute_local_command_and_capture_output(const std::string &command, std::string &output)
{
std::string full_cmd = command + " 2>&1";
FILE *pipe = popen(full_cmd.c_str(), "r");
if (!pipe) {
std::cerr << "Error: Failed to execute command: " << command << std::endl;
return false;
}
char buffer[128];
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
output += buffer;
}
pclose(pipe);
return true;
}
bool service_runner::install() { bool service_runner::install() {
maketitle("Installing " + m_service_info.service_name + " (" + m_service_info.template_name + ") on " + m_server_name); maketitle("Installing " + m_service_info.service_name + " (" + m_service_info.template_name + ") on " + m_server_name);
@ -115,7 +139,7 @@ bool service_runner::install() {
return false; return false;
// Create service directory // Create service directory
std::string mkdir_cmd = "'mkdir -p " + mRemote_service_path + "'"; std::string mkdir_cmd = "'mkdir -p " + quote(mRemote_service_path) + "'";
if (!execute_ssh_command(mkdir_cmd, "Failed to create service directory")) if (!execute_ssh_command(mkdir_cmd, "Failed to create service directory"))
return false; return false;
@ -128,9 +152,9 @@ bool service_runner::install() {
{ {
std::cout << "Copying template files from " << tinfo.path << " to " << mRemote_service_template_path << "/" << std::endl; std::cout << "Copying template files from " << tinfo.path << " to " << mRemote_service_template_path << "/" << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " + std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " +
tinfo.path + "/ " + quote(tinfo.path + "/ ") +
m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" +
mRemote_service_template_path + "/"; quote(mRemote_service_template_path+"/");
execute_local_command(rsync_cmd,"Failed to copy template files"); execute_local_command(rsync_cmd,"Failed to copy template files");
} }
@ -143,16 +167,16 @@ bool service_runner::install() {
} }
std::cout << "Copying service files from " << local_service_path << " to " << mRemote_service_config_path << std::endl; std::cout << "Copying service files from " << local_service_path << " to " << mRemote_service_config_path << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " + std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " +
local_service_path + "/ " + quote(local_service_path + "/ ") +
m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" +
mRemote_service_config_path + "/"; quote(mRemote_service_config_path + "/");
execute_local_command(rsync_cmd,"Failed to copy service files"); execute_local_command(rsync_cmd,"Failed to copy service files");
} }
// Run install script // Run install script
{ {
std::string install_cmd = "'cd " + mRemote_service_template_path + std::string install_cmd = "'cd " + quote(mRemote_service_template_path) +
" && /bin/bash install.sh " + mRemote_service_config_path + "'"; " && /bin/bash install.sh " + quote(mRemote_service_config_path) + "'";
bool ok= execute_ssh_command(install_cmd, "Failed to run install script"); bool ok= execute_ssh_command(install_cmd, "Failed to run install script");
if (!ok) if (!ok)
return false; return false;
@ -179,8 +203,8 @@ bool service_runner::uninstall() {
bool script_exists = check_remote_file_exists(uninstall_script); bool script_exists = check_remote_file_exists(uninstall_script);
if (script_exists) { if (script_exists) {
std::string uninstall_cmd = "'cd " + mRemote_service_template_path + std::string uninstall_cmd = "'cd " + quote(mRemote_service_template_path) +
" && /bin/bash _uninstall.sh " + mRemote_service_config_path + "'"; " && /bin/bash "+quote(uninstall_script)+" "+quote(mRemote_service_config_path)+"'";
if (!execute_ssh_command(uninstall_cmd, "Failed to run uninstall script")) { if (!execute_ssh_command(uninstall_cmd, "Failed to run uninstall script")) {
std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl; std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl;
} }
@ -190,7 +214,7 @@ bool service_runner::uninstall() {
} }
// 4. Remove the service directory from the server // 4. Remove the service directory from the server
std::string rm_cmd = "'rm -rf " + mRemote_service_path + "'"; std::string rm_cmd = "'rm -rf " + quote(mRemote_service_path) + "'";
if (!execute_ssh_command(rm_cmd, "Failed to remove service directory")) { if (!execute_ssh_command(rm_cmd, "Failed to remove service directory")) {
return false; return false;
} }
@ -241,8 +265,8 @@ bool service_runner::run_command(const std::string& command) {
return backup(); return backup();
// Run the generic command // Run the generic command
std::string run_cmd = "'cd " + mRemote_service_template_path + std::string run_cmd = "'cd " + quote(mRemote_service_template_path) +
" && /bin/bash " + script_path + " " + mRemote_service_config_path + "'"; " && /bin/bash "+quote(script_path)+" "+quote(mRemote_service_config_path)+"'";
return execute_ssh_command(run_cmd, "Command returned error code: " + script_path); return execute_ssh_command(run_cmd, "Command returned error code: " + script_path);
} }
@ -269,7 +293,7 @@ bool service_runner::backup() {
// Create backups directory on server if it doesn't exist // Create backups directory on server if it doesn't exist
std::string server_backups_dir = m_server_env->get_DROPSHELL_DIR() + "/backups"; std::string server_backups_dir = m_server_env->get_DROPSHELL_DIR() + "/backups";
std::string mkdir_cmd = "'mkdir -p " + server_backups_dir + "'"; std::string mkdir_cmd = "'mkdir -p " + quote(server_backups_dir) + "'";
if (!execute_ssh_command(mkdir_cmd, "Failed to create backups directory on server")) { if (!execute_ssh_command(mkdir_cmd, "Failed to create backups directory on server")) {
return false; return false;
} }
@ -296,8 +320,8 @@ bool service_runner::backup() {
std::string local_backup_path = (fs::path(local_backups_dir) / backup_filename).string(); std::string local_backup_path = (fs::path(local_backups_dir) / backup_filename).string();
// Run backup script // Run backup script
std::string backup_cmd = "'cd " + mRemote_service_template_path + std::string backup_cmd = "'cd " + quote(mRemote_service_template_path) +
" && /bin/bash \""+script_path+"\" " + mRemote_service_env_file + " " + server_backup_path + "'"; " && /bin/bash "+quote(script_path)+" "+quote(mRemote_service_env_file)+" "+quote(server_backup_path)+"'";
if (!execute_ssh_command(backup_cmd, "Backup script failed")) { if (!execute_ssh_command(backup_cmd, "Backup script failed")) {
return false; return false;
} }
@ -305,7 +329,7 @@ bool service_runner::backup() {
// Copy backup file from server to local // Copy backup file from server to local
std::string scp_cmd = "scp -P " + m_server_env->get_SSH_PORT() + " " + std::string scp_cmd = "scp -P " + m_server_env->get_SSH_PORT() + " " +
m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" +
server_backup_path + " " + local_backup_path; quote(server_backup_path) + " " + quote(local_backup_path);
if (!execute_local_command(scp_cmd, "Failed to copy backup file from server")) { if (!execute_local_command(scp_cmd, "Failed to copy backup file from server")) {
return false; return false;
} }
@ -314,7 +338,73 @@ bool service_runner::backup() {
return true; return true;
} }
service_runner::HealthStatus service_runner::is_healthy() std::map<std::string, ServiceStatus> service_runner::get_all_services_status(std::string server_name)
{
std::map<std::string, ServiceStatus> status;
std::string command = "_allservicesstatus.sh";
std::string service_name = "dropshell-agent";
if (!template_command_exists(service_name, command))
{
std::cerr << "Error: " << service_name << " does not contain the _allservicesstatus.sh script" << std::endl;
return status;
}
std::string remote_service_path = get_remote_service_path(server_name, service_name);
std::string remote_service_config_path = get_remote_service_config_path(server_name, service_name);
std::string remote_service_template_path = get_remote_service_template_path(server_name, service_name);
std::string remote_service_env_file = get_remote_service_env_file(server_name, service_name);
std::string script_path = remote_service_template_path + "/" + command;
server_env env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment" << std::endl;
return status;
}
std::string ssh_cmd = construct_ssh_cmd(env) + "'cd " + quote(remote_service_template_path) +
" && /bin/bash "+quote(script_path)+" "+quote(remote_service_config_path)+"'";
std::string output;
if (!execute_local_command_and_capture_output(ssh_cmd, 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<std::string> ports = string2multi(value);
for (const auto& port : ports)
status[service_name].ports.push_back(std::stoi(port));
}
}
}
return status;
}
HealthStatus service_runner::is_healthy()
{ {
if (!m_server_env) { if (!m_server_env) {
std::cerr << "Error: Server service not initialized" << std::endl; std::cerr << "Error: Server service not initialized" << std::endl;
@ -360,9 +450,8 @@ std::string service_runner::healthtick()
return yellow_exclamation; return yellow_exclamation;
} }
std::string service_runner::healthmark() std::string service_runner::HealthStatus2String(HealthStatus status)
{ {
HealthStatus status = is_healthy();
if (status == HealthStatus::HEALTHY) if (status == HealthStatus::HEALTHY)
return ":tick:"; return ":tick:";
else if (status == HealthStatus::UNHEALTHY) else if (status == HealthStatus::UNHEALTHY)
@ -375,6 +464,12 @@ std::string service_runner::healthmark()
return ":error:"; return ":error:";
} }
std::string service_runner::healthmark()
{
HealthStatus status = is_healthy();
return HealthStatus2String(status);
}
std::vector<int> service_runner::get_ports() std::vector<int> service_runner::get_ports()
{ {
std::vector<int> ports; std::vector<int> ports;

View File

@ -3,6 +3,9 @@
// manage a service on a server // manage a service on a server
// //
#ifndef SERVICE_RUNNER_HPP
#define SERVICE_RUNNER_HPP
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory> #include <memory>
@ -11,6 +14,19 @@
namespace dropshell { namespace dropshell {
typedef enum HealthStatus {
HEALTHY,
UNHEALTHY,
NOTINSTALLED,
ERROR,
UNKNOWN
} HealthStatus;
typedef struct ServiceStatus {
HealthStatus health;
std::vector<int> ports;
} ServiceStatus;
class service_runner { class service_runner {
public: public:
service_runner(); service_runner();
@ -30,13 +46,7 @@ class service_runner {
// check health of service. Silent. // check health of service. Silent.
// 1. run status.sh on the server // 1. run status.sh on the server
// 2. return the output of the status.sh script // 2. return the output of the status.sh script
enum class HealthStatus {
HEALTHY,
UNHEALTHY,
NOTINSTALLED,
ERROR,
UNKNOWN
};
HealthStatus is_healthy(); HealthStatus is_healthy();
// get the ports of the service // get the ports of the service
@ -47,6 +57,9 @@ class service_runner {
std::string healthtick(); std::string healthtick();
std::string healthmark(); std::string healthmark();
// get the status of all services on the server
static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
static std::string HealthStatus2String(HealthStatus status);
private: private:
// install the service over ssh, using the credentials from server.env (via server_env.hpp), by: // install the service over ssh, using the credentials from server.env (via server_env.hpp), by:
@ -73,6 +86,7 @@ class service_runner {
bool backup(); bool backup();
private: private:
std::string m_server_name; std::string m_server_name;
ServiceInfo m_service_info; ServiceInfo m_service_info;
@ -84,12 +98,18 @@ class service_runner {
std::string mRemote_service_env_file; std::string mRemote_service_env_file;
// Helper methods // Helper methods
static std::string construct_ssh_cmd(const server_env &env);
std::string construct_ssh_cmd() const; std::string construct_ssh_cmd() const;
bool check_remote_dir_exists(const std::string& dir_path) const; 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_file_exists(const std::string& file_path) const;
bool check_remote_items_exist(const std::vector<std::string>& file_paths) const; bool check_remote_items_exist(const std::vector<std::string>& file_paths) const;
bool execute_ssh_command(const std::string& command, const std::string& error_msg) const; bool execute_ssh_command(const std::string& command, const std::string& error_msg) const;
bool execute_local_command(const std::string& command, const std::string& error_msg) const; bool execute_local_command(const std::string& command, const std::string& error_msg) const;
static bool execute_local_command_and_capture_output(const std::string& command, std::string & output);
}; };
} // namespace dropshell } // namespace dropshell
#endif // SERVICE_RUNNER_HPP

View File

@ -39,7 +39,6 @@ namespace dropshell {
std::string get_remote_service_backups_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_service_env_file(const std::string &server_name, const std::string &service_name); std::string get_remote_service_env_file(const std::string &server_name, const std::string &service_name);
std::string get_remote_service_env_file_parent(const std::string &server_name, const std::string &service_name);
} // namespace dropshell } // namespace dropshell
#endif #endif

View File

@ -1,4 +1,5 @@
#include "envmanager.hpp" #include "envmanager.hpp"
#include "utils/utils.hpp"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <algorithm> #include <algorithm>
@ -97,20 +98,6 @@ void envmanager::clear_variables() {
m_variables.clear(); m_variables.clear();
} }
std::string trim(std::string str) {
// Trim leading whitespace
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
// Trim trailing whitespace
str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), str.end());
return str;
}
std::string envmanager::expand_patterns(std::string str) const { std::string envmanager::expand_patterns(std::string str) const {
// Combined regex pattern for both ${var} and $var formats // Combined regex pattern for both ${var} and $var formats
std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))"); std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))");
@ -132,62 +119,4 @@ std::string envmanager::expand_patterns(std::string str) const {
return result; return result;
} }
std::string multi2string(std::vector<std::string> values)
{
std::string result;
for (const auto& value : values) {
// remove any " contained in the string value, if present
std::string quoteless_value = value;
quoteless_value.erase(std::remove(quoteless_value.begin(), quoteless_value.end(), '"'), quoteless_value.end());
result += "\"" + trim(quoteless_value) + "\",";
}
if (!result.empty())
result.pop_back(); // Remove the last comma
return result;
}
std::vector<std::string> string2multi(std::string values)
{
std::vector<std::string> result;
// Return values separated by commas, but ignore commas within quotes
bool inside_quotes = false;
std::string current_item;
for (char c : values) {
if (c == '"') {
inside_quotes = !inside_quotes;
} else if (c == ',' && !inside_quotes) {
if (!current_item.empty()) {
// Remove quotes if present
if (current_item.front() == '"' && current_item.back() == '"') {
current_item = current_item.substr(1, current_item.length() - 2);
}
std::string final = trim(current_item);
if (!final.empty()) {
result.push_back(final);
}
current_item.clear();
}
} else {
current_item += c;
}
}
// Add the last item if not empty
if (!current_item.empty()) {
// Remove quotes if present
if (current_item.front() == '"' && current_item.back() == '"') {
current_item = current_item.substr(1, current_item.length() - 2);
}
std::string final = trim(current_item);
if (!final.empty()) {
result.push_back(final);
}
}
return result;
}
} // namespace dropshell } // namespace dropshell

View File

@ -46,6 +46,7 @@ class envmanager {
// utility functions // utility functions
std::string trim(std::string str); std::string trim(std::string str);
std::string dequote(std::string str);
std::string multi2string(std::vector<std::string> values); std::string multi2string(std::vector<std::string> values);
std::vector<std::string> string2multi(std::string values); std::vector<std::string> string2multi(std::string values);

View File

@ -3,7 +3,7 @@
#include <string> #include <string>
#include <fstream> #include <fstream>
#include <vector> #include <vector>
#include <algorithm>
namespace dropshell { namespace dropshell {
void maketitle(const std::string& title) { void maketitle(const std::string& title) {
@ -42,4 +42,92 @@ bool replace_line_in_file(const std::string& file_path, const std::string& searc
return true; return true;
} }
std::string trim(std::string str) {
// Trim leading whitespace
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
// Trim trailing whitespace
str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), str.end());
return str;
}
std::string dequote(std::string str)
{
if (str.length() < 2)
return str;
if (str.front() == '"' && str.back() == '"') {
return str.substr(1, str.length() - 2);
}
return str;
}
std::string quote(std::string str)
{
return "\""+str+"\"";
}
std::string multi2string(std::vector<std::string> values)
{
std::string result;
for (const auto& value : values) {
// remove any " contained in the string value, if present
std::string quoteless_value = value;
quoteless_value.erase(std::remove(quoteless_value.begin(), quoteless_value.end(), '"'), quoteless_value.end());
result += "\"" + trim(quoteless_value) + "\",";
}
if (!result.empty())
result.pop_back(); // Remove the last comma
return result;
}
std::vector<std::string> string2multi(std::string values)
{
std::vector<std::string> result;
// Return values separated by commas, but ignore commas within quotes
bool inside_quotes = false;
std::string current_item;
for (char c : values) {
if (c == '"') {
inside_quotes = !inside_quotes;
} else if (c == ',' && !inside_quotes) {
if (!current_item.empty()) {
// Remove quotes if present
if (current_item.front() == '"' && current_item.back() == '"') {
current_item = current_item.substr(1, current_item.length() - 2);
}
std::string final = trim(current_item);
if (!final.empty()) {
result.push_back(final);
}
current_item.clear();
}
} else {
current_item += c;
}
}
// Add the last item if not empty
if (!current_item.empty()) {
// Remove quotes if present
if (current_item.front() == '"' && current_item.back() == '"') {
current_item = current_item.substr(1, current_item.length() - 2);
}
std::string final = trim(current_item);
if (!final.empty()) {
result.push_back(final);
}
}
return result;
}
} // namespace dropshell } // namespace dropshell

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector>
namespace dropshell { namespace dropshell {
@ -13,4 +14,13 @@ void maketitle(const std::string& title);
bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line); bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line);
// utility functions
std::string trim(std::string str);
std::string dequote(std::string str);
std::string quote(std::string str);
std::string multi2string(std::vector<std::string> values);
std::vector<std::string> string2multi(std::string values);
} // namespace dropshell } // namespace dropshell

View File

@ -0,0 +1,41 @@
#!/bin/bash
# BACKUP SCRIPT
# The backup script is OPTIONAL.
# It is used to backup the service on the server.
# It is called with:
# 1) the path to the server specific env file as the frist argument.
# 2) the path to the destination backup file as the second argument.
# If the backup file already exists, the script should exit with a message.
source "$(dirname "$0")/_common.sh"
load_env "$1" || die "Failed to load environment variables"
# Required environment variables
check_required_env_vars "CONTAINER_NAME" "LOCAL_DATA_FOLDER"
# Get backup file path from second argument
BACKUP_FILE="$2"
if [ -z "$BACKUP_FILE" ]; then
die "Backup file path not provided"
fi
# Check if backup file already exists
if [ -f "$BACKUP_FILE" ]; then
die "Backup file $BACKUP_FILE already exists"
fi
# Stop container before backup
_stop_container "$CONTAINER_NAME"
Create backup of data folder
echo "Creating backup of $LOCAL_DATA_FOLDER..."
if ! tar zcvf "$BACKUP_FILE" -C "$LOCAL_DATA_FOLDER" .; then
_start_container "$CONTAINER_NAME"
die "Failed to create backup"
fi
# Start container after backup
_start_container "$CONTAINER_NAME"
echo "Backup created successfully: $BACKUP_FILE"