Working on backup/restore.
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled

This commit is contained in:
Your Name 2025-05-18 20:19:47 +12:00
parent 263edd9b50
commit fb6974b51a
7 changed files with 583 additions and 469 deletions

View File

@ -16,157 +16,159 @@
#include "utils/directories.hpp"
#include "shared_commands.hpp"
namespace dropshell
{
namespace dropshell {
int backupdata_handler(const CommandContext &ctx);
int backupdata_handler(const CommandContext& ctx);
static std::vector<std::string> backupdata_name_list = {"backupdata", "bd", "backup", "bup"};
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
2, // min_args (after command)
2, // max_args (after command)
"backupdata SERVER SERVICE",
"Backup data for a service on a server.",
// heredoc
R"(
// 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
2, // min_args (after command)
2, // 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;
)"});
}
} 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())
namespace shared_commands
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
LocalServiceInfo sinfo = get_service_info(server, service);
if (!SIvalid(sinfo))
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;
}
} // namespace shared_commands
int backupdata_handler(const CommandContext &ctx)
{
error << "Service " << service << " is not valid" << std::endl;
return false;
}
ASSERT(ctx.args.size() == 2, "Invalid number of arguments");
const std::string command = "backup";
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
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;
if (service == "all")
{
// 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 LocalServiceInfo &si : services)
okay &= shared_commands::backupdata_service(server, si.service_name);
return okay ? 0 : 1;
}
// 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;
return shared_commands::backupdata_service(server, service) ? 0 : 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

View File

@ -11,7 +11,7 @@ namespace dropshell
{
int create_service_handler(const CommandContext &ctx);
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent);
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name);
void create_service_autocomplete(const CommandContext &ctx);
static std::vector<std::string> create_service_name_list = {"create-service"};
@ -45,7 +45,7 @@ namespace dropshell
std::string service = safearg(ctx.args, 1);
std::string template_name = safearg(ctx.args, 2);
return create_service(server, template_name, service, false) ? 0 : 1;
return create_service(server, template_name, service) ? 0 : 1;
}
void create_service_autocomplete(const CommandContext &ctx)
@ -58,12 +58,12 @@ namespace dropshell
{
std::set<std::string> templates = gTemplateManager().get_template_list();
for (const auto &template_name : templates)
std::cout << template_name << std::endl;
rawout << template_name << std::endl;
}
}
}
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent)
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name)
{
if (server_name.empty() || template_name.empty() || service_name.empty())
return false;
@ -72,44 +72,34 @@ namespace dropshell
if (service_dir.empty())
{
if (!silent)
{
std::cerr << "Error: Couldn't locate server " << server_name << " in any config directory" << std::endl;
std::cerr << "Please check the server name is correct and try again" << std::endl;
std::cerr << "You can list all servers with 'dropshell servers'" << std::endl;
std::cerr << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
}
error << "Couldn't locate server " << server_name << " in any config directory" << std::endl;
info << "Please check the server name is correct and try again" << std::endl;
info << "You can list all servers with 'dropshell servers'" << std::endl;
info << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
return false;
}
if (std::filesystem::exists(service_dir))
{
if (!silent)
{
std::cerr << "Error: Service already exists: " << service_name << std::endl;
std::cerr << "Current service path: " << service_dir << std::endl;
}
error << "Service already exists: " << service_name << std::endl;
debug << "Current service path: " << service_dir << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(template_name);
if (!tinfo.is_set())
{
if (!silent)
{
std::cerr << "Error: Template '" << template_name << "' not found" << std::endl;
std::cerr << "Please check the template name is correct and try again" << std::endl;
std::cerr << "You can list all templates with 'dropshell templates'" << std::endl;
std::cerr << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
}
error << "Template '" << template_name << "' not found" << std::endl;
info << "Please check the template name is correct and try again" << std::endl;
info << "You can list all templates with 'dropshell templates'" << std::endl;
info << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
return false;
}
// check template is all good.
if (!gTemplateManager().test_template(tinfo.local_template_path()))
{
if (!silent)
std::cerr << "Error: Template '" << template_name << "' is not valid" << std::endl;
error << "Template '" << template_name << "' is not valid" << std::endl;
return false;
}
@ -119,14 +109,11 @@ namespace dropshell
// copy the template config files to the service directory
recursive_copy(tinfo.local_template_path() / "config", service_dir);
if (!silent)
{
std::cout << "Service " << service_name << " created successfully" << std::endl;
std::cout << std::endl;
std::cout << "To complete the installation, please:" << std::endl;
std::cout << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl;
std::cout << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
}
info << "Service " << service_name << " created successfully" << std::endl;
info << std::endl;
info << "To complete the installation, please:" << std::endl;
info << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl;
info << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
return true;
}

View File

@ -53,84 +53,87 @@ namespace dropshell
}
} install_command_register;
// ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool install_service(const std::string &server, const std::string &service, bool silent)
namespace shared_commands
{
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
return false;
server_env_manager server_env(server);
if (!server_env.is_valid())
return false;
maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
if (!server_env.is_valid())
return false; // should never hit this.
// Check if template exists
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
if (!tinfo.is_set())
return false;
if (!tinfo.template_valid())
// ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool install_service(const std::string &server, const std::string &service)
{
std::cerr << "Template is not valid: " << service_info.template_name << std::endl;
return false;
}
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
return false;
// Create service directory
std::string remote_service_path = remotepath::service(server, service);
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
{
std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
return false;
}
server_env_manager server_env(server);
if (!server_env.is_valid())
return false;
// Check if rsync is installed on remote host
std::string check_rsync_cmd = "which rsync";
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent))
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
}
maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
// Copy template files
debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl
if (!server_env.is_valid())
return false; // should never hit this.
// Check if template exists
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
if (!tinfo.is_set())
return false;
if (!tinfo.template_valid())
{
std::cerr << "Template is not valid: " << service_info.template_name << std::endl;
return false;
}
// Create service directory
std::string remote_service_path = remotepath::service(server, service);
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
{
std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
return false;
}
// Check if rsync is installed on remote host
std::string check_rsync_cmd = "which rsync";
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent))
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
}
// Copy template files
debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_template(server, service) << "/" << std::endl;
if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service),
server_env, silent))
{
std::cerr << "Failed to copy template files using rsync" << std::endl;
return false;
}
if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service),
server_env, false))
{
std::cerr << "Failed to copy template files using rsync" << std::endl;
return false;
}
// Copy service files
debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl
// Copy service files
debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_config(server, service) << std::endl;
if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service),
server_env, silent))
{
std::cerr << "Failed to copy service files using rsync" << std::endl;
return false;
if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service),
server_env, false))
{
std::cerr << "Failed to copy service files using rsync" << std::endl;
return false;
}
// Run install script
{
info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, false, {});
}
// print health tick
info << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true;
}
// Run install script
{
info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, silent, {});
}
// print health tick
info << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true;
}
} // namespace shared_commands
// ------------------------------------------------------------------------------------------------
// update_dropshell
@ -305,14 +308,15 @@ namespace dropshell
info << "done." << std::endl;
// add in bb64. We can't use execute_remote_command() here, as that relies on bb64 which we're installing!
info << "Installing bb64 on " << server << "..." << std::endl << std::flush;
info << "Installing bb64 on " << server << "..." << std::endl
<< std::flush;
std::string remote_cmd =
"ssh -p " + server_env.get_SSH_INFO().port + " " + server_env.get_SSH_INFO().user + "@" + server_env.get_SSH_INFO().host +
" 'mkdir -p " + quote(agent_path) + " && curl -fsSL \"https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh\" | bash -s -- " +
quote(agent_path) + " " + quote("$(id -u " + server_env.get_SSH_USER() + "):$(id -g " + server_env.get_SSH_USER() + ")") + "'";
//std::cout << "Executing: " << remote_cmd << std::endl;
// std::cout << "Executing: " << remote_cmd << std::endl;
if (!execute_local_command("", remote_cmd, {}, nullptr, cMode::Silent))
error << "Failed to download bb64 to " << agent_path << " on remote server." << std::endl;
else
@ -362,7 +366,7 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
{
if (!install_service(server, service.service_name, false))
if (!shared_commands::install_service(server, service.service_name))
okay = false;
}
return okay ? 0 : 1;
@ -370,7 +374,7 @@ namespace dropshell
else
{ // install the specific service.
std::string service = safearg(ctx.args, 1);
return install_service(server, service, false) ? 0 : 1;
return shared_commands::install_service(server, service) ? 0 : 1;
}
}

View File

@ -10,27 +10,29 @@
#include "utils/assert.hpp"
namespace dropshell {
namespace dropshell
{
int nuke_handler(const CommandContext& ctx);
static std::vector<std::string> nuke_name_list={"nuke"};
int nuke_handler(const CommandContext &ctx);
static std::vector<std::string> nuke_name_list = {"nuke"};
// Static registration
struct NukeCommandRegister {
NukeCommandRegister() {
CommandRegistry::instance().register_command({
nuke_name_list,
nuke_handler,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
2, // min_args (after command)
2, // max_args (after command)
"nuke SERVER SERVICE|all",
"Nuke a service on a server. Destroys everything, both local and remote!",
// heredoc
R"(
// Static registration
struct NukeCommandRegister
{
NukeCommandRegister()
{
CommandRegistry::instance().register_command({nuke_name_list,
nuke_handler,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
2, // min_args (after command)
2, // max_args (after command)
"nuke SERVER SERVICE|all",
"Nuke a service on a server. Destroys everything, both local and remote!",
// heredoc
R"(
Nuke a service.
Examples:
@ -41,109 +43,112 @@ struct NukeCommandRegister {
both on the dropshell host and on the remote server.
Use with caution!
)"
});
}
} nuke_command_register;
)"});
}
} nuke_command_register;
int nuke_one(std::string server, std::string service)
{
server_env_manager server_env(server);
// step 1 - nuke on remote server.
if (server_env.is_valid())
namespace shared_commands
{
LocalServiceInfo service_info;
service_info = get_service_info(server, service);
if (!SIvalid(service_info))
error << "Warning: Invalid service: " << service << std::endl;
if (server_env.check_remote_dir_exists(remotepath::service(server, service)))
bool nuke_service(const std::string &server, const std::string &service)
{
// run the nuke script on the remote server if it exists.
// otherwise just uninstall.
if (gTemplateManager().template_command_exists(service_info.template_name, "nuke"))
server_env_manager server_env(server);
// step 1 - nuke on remote server.
if (server_env.is_valid())
{
info << "Running nuke script for " << service << " on " << server << std::endl;
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
warning << "Failed to run nuke script: " << service << std::endl;
LocalServiceInfo service_info;
service_info = get_service_info(server, service);
if (!SIvalid(service_info))
error << "Warning: Invalid service: " << service << std::endl;
if (server_env.check_remote_dir_exists(remotepath::service(server, service)))
{
// run the nuke script on the remote server if it exists.
// otherwise just uninstall.
if (gTemplateManager().template_command_exists(service_info.template_name, "nuke"))
{
info << "Running nuke script for " << service << " on " << server << std::endl;
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
warning << "Failed to run nuke script: " << service << std::endl;
}
else
{
info << "No nuke script found for " << service << " on " << server << std::endl;
info << "Running uninstall script instead and will clean directories." << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Failed to uninstall service: " << service << std::endl;
}
// Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), true))
{
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
info << "Remote service directory removed: " << remotepath::service(server, service) << std::endl;
}
else
warning << "Failed to remove remote service directory" << std::endl;
}
else
warning << "Service not found on remote server: " << remotepath::service(server, service) << std::endl;
}
else
warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory.
std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{
warning << "Local service directory not found: " << local_service_path << std::endl;
}
else
{
info << "No nuke script found for " << service << " on " << server << std::endl;
info << "Running uninstall script instead and will clean directories." << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Failed to uninstall service: " << service << std::endl;
auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0)
error << "Failed to remove local service directory" << std::endl;
}
// Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), true))
info << "Nuked service " << service << " on server " << server << std::endl;
return true;
}
} // namespace shared_commands
int nuke_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 2, "Usage: nuke SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")");
ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration.");
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
int rval = 0;
// iterate through all service folders in the server directory.
std::string server_path = localpath::server(server);
if (server_path.empty())
{
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
info << "Remote service directory removed: " << remotepath::service(server, service) << std::endl;
error << "Server not found: " << server << std::endl;
return 1;
}
else
warning << "Failed to remove remote service directory" << std::endl;
for (const auto &entry : std::filesystem::directory_iterator(server_path))
{
if (entry.is_directory() && entry.path().filename().string().find(".") != 0)
{
std::string service_name = entry.path().filename().string();
rval |= (shared_commands::nuke_service(server, service_name) ? 0 : 1);
}
}
return rval;
}
else
warning << "Service not found on remote server: " << remotepath::service(server, service) << std::endl;
}
else
warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory.
std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{
warning << "Local service directory not found: " << local_service_path << std::endl;
}
else
{
auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0)
error << "Failed to remove local service directory" << std::endl;
}
info << "Nuked service " << service << " on server " << server << std::endl;
return 0;
}
int nuke_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 2, "Usage: nuke SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")");
ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration.");
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
int rval = 0;
// iterate through all service folders in the server directory.
std::string server_path = localpath::server(server);
if (server_path.empty())
{
error << "Server not found: " << server << std::endl;
return 1;
return (shared_commands::nuke_service(server, service) ? 0 : 1);
}
for (const auto& entry : std::filesystem::directory_iterator(server_path))
{
if (entry.is_directory() && entry.path().filename().string().find(".") != 0)
{
std::string service_name = entry.path().filename().string();
rval |= nuke_one(server, service_name);
}
}
return rval;
}
else
{
return nuke_one(server, service);
}
}
} // namespace dropshell

View File

@ -16,122 +16,228 @@
#include "utils/directories.hpp"
#include "shared_commands.hpp"
namespace dropshell
{
namespace dropshell {
int restoredata_handler(const CommandContext &ctx);
int restoredata_handler(const CommandContext& ctx);
void restoredata_autocomplete(const CommandContext &ctx);
void restoredata_autocomplete(const CommandContext& ctx);
static std::vector<std::string> restoredata_name_list = {"restoredata", "rd", "restore", "rest"};
static std::vector<std::string> restoredata_name_list={"restoredata","rd","restore","rest"};
// Static registration
struct RestoreDataCommandRegister {
RestoreDataCommandRegister() {
CommandRegistry::instance().register_command({
restoredata_name_list,
restoredata_handler,
restoredata_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
3, // min_args (after command)
3, // max_args (after command)
"restoredata SERVER SERVICE BACKUP_FILE",
"Restore data for a service on a server, overwriting the existing data.",
// heredoc
R"(
// Static registration
struct RestoreDataCommandRegister
{
RestoreDataCommandRegister()
{
CommandRegistry::instance().register_command({restoredata_name_list,
restoredata_handler,
restoredata_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
3, // min_args (after command)
3, // max_args (after command)
"restoredata SERVER SERVICE BACKUP_FILE|latest",
"Restore data for a service on a server, overwriting the existing data.",
// heredoc
R"(
restoredata SERVER SERVICE BACKUP_FILE Restore data to a service on a server. Destructive.
restoredata SERVER SERVICE latest Restore the latest backup for the given service.
Note: This command will not create any service configuration, you need
to have a valid service installed first.
The backup file must be in the local backups directory.
WARNING: This will permanently overwrite the service's data on the remote server!
)"
});
}
} restoredata_command_register;
)"});
}
} restoredata_command_register;
int restoredata_handler(const CommandContext& ctx)
{
ASSERT(ctx.args.size() == 2, "Invalid number of arguments");
std::string server = ctx.args[0];
std::string service = ctx.args[1];
std::string backup_file = ctx.args[2];
server_env_manager server_env(server);
if (!server_env.is_valid()) {
error << "Server " << server << " is not valid" << std::endl;
return 1;
}
shared_commands::cBackupFileName backup_details(backup_file);
if (!backup_details.is_valid()) {
error << "Invalid backup file: " << backup_file << std::endl;
return 1;
}
return 0;
}
void restoredata_autocomplete(const CommandContext& ctx)
{
shared_commands::std_autocomplete(ctx);
if (ctx.args.size() == 2) // next arg is the backup file
std::vector<shared_commands::cBackupFileName> get_backup_files(const std::string &server, const std::string &match_service = "", const std::string &match_template_name = "")
{
std::string server = ctx.args[0];
std::string service = ctx.args[1];
std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return {};
}
std::vector<shared_commands::cBackupFileName> backups;
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info)) {
error << "Service " << service << " is not valid" << std::endl;
return;
}
std::string template_name = service_info.template_name;
std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return;
}
for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
if (!entry.is_regular_file()) continue;
for (const auto &entry : std::filesystem::directory_iterator(local_backups_dir))
{
if (!entry.is_regular_file())
continue;
std::string filename = entry.path().filename().string();
shared_commands::cBackupFileName backup_details(filename);
if (backup_details.is_valid()) {
if (backup_details.get_template_name() == template_name) {
backups.push_back(backup_details);
}
}
if (backup_details.is_valid())
if (match_service.empty() || backup_details.get_service() == match_service)
if (match_template_name.empty() || backup_details.get_template_name() == match_template_name)
backups.push_back(backup_details);
}
// sort backups by datetime
std::sort(backups.begin(), backups.end(), [](const shared_commands::cBackupFileName& a, const shared_commands::cBackupFileName& b) {
return a.get_datetime() > b.get_datetime();
});
std::sort(backups.begin(), backups.end(), [](const shared_commands::cBackupFileName &a, const shared_commands::cBackupFileName &b)
{ return a.get_datetime() > b.get_datetime(); });
// print most recent backup for each {host,service} pair
std::map<std::string, std::string> unique_backups;
for (const auto& backup : backups) {
std::string key = backup.get_server() + "-" + backup.get_service();
if (unique_backups.find(key) == unique_backups.end()) {
unique_backups[key] = backup.get_filename();
}
return backups;
}
int restoredata_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 3, "Invalid number of arguments");
std::string server = ctx.args[0];
std::string service = ctx.args[1];
std::string backup_file = ctx.args[2];
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return 1;
}
for (const auto& [key, value] : unique_backups) {
rawout << value << std::endl;
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
{
error << "Service " << service << " is not valid" << std::endl;
return 1;
}
std::optional<shared_commands::cBackupFileName> backup_details;
if (backup_file == "latest")
{ // special case.
std::vector<shared_commands::cBackupFileName> backups = get_backup_files(server, service, service_info.template_name); // this service only (and also match template in case something changed there!).
if (backups.empty())
{
error << "No backups found for " << server << "/" << service << std::endl;
debug << "Template also has to match with the service template: " << service_info.template_name << std::endl;
return 1;
}
backup_details = backups[0];
} else {
backup_details = shared_commands::cBackupFileName(backup_file);
}
bool backup_details_okay = backup_details.has_value() && backup_details->is_valid();
if (!backup_details_okay)
{
error << "Invalid backup file: " << backup_file << std::endl;
return 1;
}
std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return 1;
}
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string();
if (!std::filesystem::exists(local_backup_file_path))
{
error << "Error: Backup file not found at " << local_backup_file_path << std::endl;
return 1;
}
if (backup_details->get_template_name() != service_info.template_name)
{
error << "Error: Backup template does not match service template. Can't restore." << std::endl;
debug << "Backup template: " << backup_details->get_template_name() << std::endl;
debug << "Service template: " << service_info.template_name << std::endl;
debug << "Backup file: " << local_backup_file_path << std::endl;
return 1;
}
info << "Restoring " << backup_details->get_datetime() << " backup of " << backup_details->get_service() << " taken from " << backup_details->get_server() << ", onto " << server << "/" << service << std::endl;
info << std::endl;
warning << "*** ALL DATA FOR " << server << "/" << service << " WILL BE OVERWRITTEN! ***" << std::endl;
// run the restore script
info << "OK, here goes..." << std::endl;
{ // backup existing service
info << "1) Backing up old service... " << std::endl;
if (!shared_commands::backupdata_service(server, service))
{
error << "Error: Backup failed, restore aborted." << std::endl;
error << "You can try using dropshell install " << server << " " << service << " to install the service afresh." << std::endl;
error << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl;
return 1;
}
info << "Backup complete." << std::endl;
}
{ // nuke the old service
info << "2) Nuking old service..." << std::endl;
if (!shared_commands::nuke_service(server, service))
return 1;
}
{ // restore service from backup
info << "3) Restoring service data from backup..." << std::endl;
std::string remote_backups_dir = remotepath::backups(server);
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// Copy backup file from local to server
if (!shared_commands::scp_file_to_remote(server_env, local_backup_file_path, remote_backup_file_path, false))
{
error << "Failed to copy backup file from local to server" << std::endl;
return 1;
}
shared_commands::cRemoteTempFolder remote_temp_folder(server_env);
server_env.run_remote_template_command(service, "restore", {}, false, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}});
} // dtor of remote_temp_folder will clean up the temp folder on the server
{ // installing fresh service
info << "4) Non-destructive install of fresh service..." << std::endl;
if (!shared_commands::install_service(server, service))
return 1;
}
{ // healthcheck the service
info << "5) Healthchecking service..." << std::endl;
std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m";
bool healthy = (server_env.run_remote_template_command(service, "status", {}, false, {}));
info << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
}
return 0;
}
void restoredata_autocomplete(const CommandContext &ctx)
{
shared_commands::std_autocomplete(ctx);
if (ctx.args.size() == 2) // next arg is the backup file
{
std::string server = ctx.args[0];
std::string service = ctx.args[1];
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
{
error << "Service " << service << " is not valid" << std::endl;
return;
}
std::string template_name = service_info.template_name;
std::vector<shared_commands::cBackupFileName> backups = get_backup_files(server, "", template_name); // any service, but must match template.
// print most recent backup for each {host,service} pair
std::map<std::string, std::string> unique_backups;
for (const auto &backup : backups)
{
std::string key = backup.get_server() + "-" + backup.get_service();
if (unique_backups.find(key) == unique_backups.end())
unique_backups[key] = backup.get_filename();
}
for (const auto &[key, value] : unique_backups)
rawout << value << std::endl;
}
}
}
} // namespace dropshell

View File

@ -81,6 +81,18 @@ namespace dropshell
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);
// defined in backupdata.cpp, used by restoredata.cpp.
bool backupdata_service(const std::string& server, const std::string& service);
// defined in uninstall.cpp
bool uninstall_service(const std::string &server, const std::string &service);
// defined in nuke.cpp
bool nuke_service(const std::string &server, const std::string &service);
// defined in install.cpp
bool install_service(const std::string &server, const std::string &service);
} // namespace shared_commands
} // namespace dropshell
#endif

View File

@ -41,45 +41,43 @@ namespace dropshell
}
} uninstall_command_register;
bool uninstall_service(const std::string &server, const std::string &service, bool silent = false)
{
if (!silent)
namespace shared_commands {
bool uninstall_service(const std::string &server, const std::string &service)
{
maketitle("Uninstalling " + service + " on " + server);
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Invalid server: " << server << std::endl;
return false; // should never hit this.
}
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Invalid server: " << server << std::endl;
return false; // should never hit this.
}
// 2. Check if service directory exists on server
if (!server_env.check_remote_dir_exists(remotepath::service(server, service)))
{
error << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall
}
// 2. Check if service directory exists on server
if (!server_env.check_remote_dir_exists(remotepath::service(server, service)))
{
error << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall
}
// 3. Run uninstall script if it exists
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, silent, {}))
if (!silent)
// 3. Run uninstall script if it exists
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Uninstall script failed, but continuing with directory removal" << std::endl;
// 4. Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), silent))
{
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
if (!silent)
// 4. Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), false))
{
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
info << "Removed remote service directory " << remotepath::service(server, service) << std::endl;
}
else if (!silent)
warning << "Failed to remove remote service directory" << std::endl;
}
else
warning << "Failed to remove remote service directory" << std::endl;
if (!silent)
info << "Completed service " << service << " uninstall on " << server << std::endl;
return true;
}
return true;
}
} // namespace shared_commands
int uninstall_handler(const CommandContext &ctx)
{
@ -98,14 +96,14 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
{
if (!uninstall_service(server, service.service_name))
if (!shared_commands::uninstall_service(server, service.service_name))
okay = false;
}
return okay ? 0 : 1;
}
std::string service = safearg(ctx.args, 1);
return uninstall_service(server, service) ? 0 : 1;
return shared_commands::uninstall_service(server, service) ? 0 : 1;
}
} // namespace dropshell