This commit is contained in:
parent
f89d90c12b
commit
630a9fd19a
172
source/src/commands/backupdata.cpp
Normal file
172
source/src/commands/backupdata.cpp
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#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<std::string> 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:"<<std::endl;
|
||||||
|
info << " dropshell restore " << server << " " << service << " " << backup_filename << std::endl;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int backupdata_handler(const CommandContext& ctx)
|
||||||
|
{
|
||||||
|
if (ctx.args.size() < 1)
|
||||||
|
{
|
||||||
|
error << "Server name is required" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string server = safearg(ctx.args, 0);
|
||||||
|
|
||||||
|
if (ctx.args.size() < 2)
|
||||||
|
{
|
||||||
|
// backup all services on the server
|
||||||
|
maketitle("Backing up data for all services on " + server);
|
||||||
|
bool okay = true;
|
||||||
|
std::vector<LocalServiceInfo> 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
|
@ -13,239 +13,318 @@ namespace dropshell
|
|||||||
namespace shared_commands
|
namespace shared_commands
|
||||||
{
|
{
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// std_autocomplete : SHARED COMMAND
|
// std_autocomplete : SHARED COMMAND
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void std_autocomplete(const CommandContext &ctx)
|
void std_autocomplete(const CommandContext &ctx)
|
||||||
{
|
|
||||||
if (ctx.args.size() == 0)
|
|
||||||
{ // just the command, no args yet.
|
|
||||||
// list servers
|
|
||||||
std::vector<ServerInfo> servers = get_configured_servers();
|
|
||||||
for (const auto &server : servers)
|
|
||||||
{
|
|
||||||
rawout << server.name << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ctx.args.size() == 1)
|
|
||||||
{
|
{
|
||||||
// list services
|
if (ctx.args.size() == 0)
|
||||||
std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]);
|
{ // just the command, no args yet.
|
||||||
for (const auto &service : services)
|
// list servers
|
||||||
{
|
std::vector<ServerInfo> servers = get_configured_servers();
|
||||||
rawout << service.service_name << std::endl;
|
for (const auto &server : servers)
|
||||||
}
|
{
|
||||||
}
|
rawout << server.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<std::string, ServiceStatus> get_all_services_status(std::string server_name)
|
|
||||||
{
|
|
||||||
std::map<std::string, ServiceStatus> 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
|
else if (ctx.args.size() == 1)
|
||||||
std::vector<std::string> ports = string2multi(value);
|
{
|
||||||
for (const auto &port : ports)
|
// list services
|
||||||
{
|
std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]);
|
||||||
if (port != "unknown")
|
for (const auto &service : services)
|
||||||
status[service_name].ports.push_back(str2int(port));
|
{
|
||||||
|
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<std::string, ServiceStatus> get_all_services_status(std::string server_name)
|
||||||
|
{
|
||||||
|
std::map<std::string, ServiceStatus> 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<std::string> 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
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// healthtick : SHARED COMMAND
|
std::string healthtick(const std::string &server, const std::string &service)
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
error << "Server service not initialized" << std::endl;
|
std::string green_tick = "\033[32m✓\033[0m";
|
||||||
return HealthStatus::ERROR;
|
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, {}))
|
// healthmark : SHARED COMMAND
|
||||||
return HealthStatus::UNHEALTHY;
|
// ------------------------------------------------------------------------------------------------
|
||||||
return HealthStatus::HEALTHY;
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
cBackupFileName::cBackupFileName(const std::string &filename)
|
||||||
// healthmark : SHARED COMMAND
|
{
|
||||||
// ------------------------------------------------------------------------------------------------
|
// Parse the filename according to the format:
|
||||||
std::string healthmark(const std::string &server, const std::string &service)
|
// server + magic_string() + template_name + magic_string() + service + magic_string() + datetime + ".tgz"
|
||||||
{
|
std::string name = filename;
|
||||||
HealthStatus status = is_healthy(server, service);
|
if (name.size() > 4 && name.substr(name.size() - 4) == ".tgz")
|
||||||
return HealthStatus2String(status);
|
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
|
} // namespace shared_commands
|
||||||
|
|
||||||
|
@ -57,6 +57,29 @@ namespace dropshell
|
|||||||
void std_autocomplete(const CommandContext &ctx);
|
void std_autocomplete(const CommandContext &ctx);
|
||||||
void std_autocomplete_allowall(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 shared_commands
|
||||||
} // namespace dropshell
|
} // namespace dropshell
|
||||||
|
@ -91,7 +91,6 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
namespace dropshell {
|
namespace dropshell {
|
||||||
|
|
||||||
static const std::string magic_string = "-_-";
|
|
||||||
|
|
||||||
service_runner::service_runner(const std::string& server_name, const std::string& service_name) :
|
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)
|
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
|
// split the backup filename into parts based on the magic string
|
||||||
std::vector<std::string> parts = dropshell::split(backup_file, magic_string);
|
std::vector<std::string> parts = dropshell::split(backup_file, "-_-");
|
||||||
if (parts.size() != 4) {
|
if (parts.size() != 4) {
|
||||||
std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
|
std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
|
||||||
return false;
|
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)
|
// backup the service over ssh, using the credentials from server.env (via server_env.hpp)
|
||||||
// 1. run backup.sh on the server
|
// 1. run backup.sh on the server
|
||||||
// 2. create a backup file with format server-service-datetime.tgz
|
// 2. create a backup file with format server-service-datetime.tgz
|
||||||
@ -452,17 +445,18 @@ bool service_runner::backup(bool silent) {
|
|||||||
std::stringstream datetime;
|
std::stringstream datetime;
|
||||||
datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
|
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
|
// 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 remote_backup_file_path = remote_backups_dir + "/" + backup_filename;
|
||||||
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string();
|
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 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
|
{ // Run backup script
|
||||||
shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
|
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
|
// 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_file;
|
||||||
std::string latest_datetime;
|
std::string latest_datetime;
|
||||||
|
|
||||||
|
@ -10,6 +10,16 @@
|
|||||||
|
|
||||||
namespace dropshell {
|
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) {
|
void maketitle(const std::string& title, sColour colour) {
|
||||||
colourstream(colour) << std::string(title.length() + 4, '-') << std::endl;
|
colourstream(colour) << std::string(title.length() + 4, '-') << std::endl;
|
||||||
colourstream(colour) << "| " << title << " |" << std::endl;
|
colourstream(colour) << "| " << title << " |" << std::endl;
|
||||||
|
@ -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);
|
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
|
// utility functions
|
||||||
std::string trim(std::string str);
|
std::string trim(std::string str);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user