Working on nuke
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled

This commit is contained in:
Your Name 2025-05-24 17:16:19 +12:00
parent 60907e5e02
commit 343e597d84
9 changed files with 54 additions and 572 deletions

View File

@ -19,8 +19,8 @@ _autocommandrun_volume() {
echo "Creating volume ${volume_name}"
docker volume create ${volume_name}
;;
nuke)
echo "Nuking volume ${volume_name}"
destroy)
echo "Destroying volume ${volume_name}"
docker volume rm ${volume_name}
;;
backup)
@ -50,14 +50,14 @@ _autocommandrun_path() {
echo "Creating path ${path}"
mkdir -p ${path}
;;
nuke)
echo "Nuking path ${path}"
destroy)
echo "Destroying path ${path}"
local path_parent=$(dirname ${path})
local path_child=$(basename ${path})
if [ -d "${path_parent}/${path_child}" ]; then
docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to nuke path ${path}"
docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to destroy path ${path}"
else
echo "Path ${path} does not exist - nothing to nuke"
echo "Path ${path} does not exist - nothing to destroy"
fi
;;
backup)
@ -95,7 +95,7 @@ _autocommandrun_file() {
mkdir -p ${filepath_parent}
fi
;;
nuke)
destroy)
rm -f ${filepath}
;;
backup)
@ -181,8 +181,8 @@ datacreate() {
}
datanuke() {
_autocommandparse nuke none "$@"
datadestroy() {
_autocommandparse destroy none "$@"
}
databackup() {

View File

@ -14,9 +14,7 @@ namespace autocomplete {
const std::set<std::string> system_commands_noargs = {"templates","autocomplete_list_servers","autocomplete_list_services","autocomplete_list_commands"};
const std::set<std::string> system_commands_always_available = {"help","edit"};
const std::set<std::string> system_commands_require_config = {"server","templates","create-service","create-template","create-server","ssh","list"};
const std::set<std::string> system_commands_hidden = {"nuke","_allservicesstatus"};
const std::set<std::string> service_commands_require_config = {"ssh","edit","nuke","_allservicesstatus"};
const std::set<std::string> system_commands_hidden = {"_allservicesstatus"};
void merge_commands(std::set<std::string> &commands, const std::set<std::string> &new_commands)
{

View File

@ -13,16 +13,16 @@
namespace dropshell
{
int nuke_handler(const CommandContext &ctx);
static std::vector<std::string> nuke_name_list = {"destroy","nuke"};
int destroy_handler(const CommandContext &ctx);
static std::vector<std::string> destroy_name_list = {"destroy"};
// Static registration
struct NukeCommandRegister
struct DestroyCommandRegister
{
NukeCommandRegister()
DestroyCommandRegister()
{
CommandRegistry::instance().register_command({nuke_name_list,
nuke_handler,
CommandRegistry::instance().register_command({destroy_name_list,
destroy_handler,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
@ -33,7 +33,7 @@ namespace dropshell
"Destroy a service on a server. Erases everything, both local and remote!",
// heredoc
R"(
Nuke a service.
Destroy a service.
Examples:
destroy SERVER SERVICE destroy the given service on the given server.
@ -45,41 +45,46 @@ namespace dropshell
Use with caution!
)"});
}
} nuke_command_register;
} destroy_command_register;
namespace shared_commands
{
bool nuke_service(const std::string &server, const std::string &service)
bool destroy_service(const std::string &server, const std::string &service)
{
ServerConfig server_env(server);
// step 1 - nuke on remote server.
// step 1 - destroy on remote server.
if (server_env.is_valid())
{
LocalServiceInfo service_info;
service_info = get_service_info(server, service);
if (!SIvalid(service_info))
error << "Invalid service: " << service << std::endl;
bool service_valid = SIvalid(service_info);
if (!service_valid)
warning << "Invalid service: " << service << std::endl;
std::string user = server_env.get_user_for_service(service);
if (server_env.check_remote_dir_exists(remotepath(server, user).service(service), user))
bool remote_dir_exists = server_env.check_remote_dir_exists(remotepath(server, user).service(service), user);
if (remote_dir_exists)
{
// run the nuke script on the remote server if it exists.
// run the destroy script on the remote server if it exists.
// otherwise just uninstall.
if (gTemplateManager().template_command_exists(service_info.template_name, "nuke"))
if (service_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;
}
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;
if (gTemplateManager().template_command_exists(service_info.template_name, "destroy"))
{
info << "Running destroy script for " << service << " on " << server << std::endl;
if (!server_env.run_remote_template_command(service, "destroy", {}, false, {}))
warning << "Failed to run destroy script: " << service << std::endl;
}
else
{
info << "No destroy 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.
@ -95,9 +100,9 @@ namespace dropshell
warning << "Service not found on remote server: " << remotepath(server, user).service(service) << std::endl;
}
else
warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
warning << "Can't destroy the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory.
// step 2 - destroy the local service directory.
std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{
@ -110,15 +115,15 @@ namespace dropshell
error << "Failed to remove local service directory" << std::endl;
}
info << "Nuked service " << service << " on server " << server << std::endl;
info << "Destroyed service " << service << " on server " << server << std::endl;
return true;
}
} // namespace shared_commands
int nuke_handler(const CommandContext &ctx)
int destroy_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(ctx.args.size() == 2, "Usage: destroy 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);
@ -141,14 +146,14 @@ namespace dropshell
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);
rval |= (shared_commands::destroy_service(server, service_name) ? 0 : 1);
}
}
return rval;
}
else
{
return (shared_commands::nuke_service(server, service) ? 0 : 1);
return (shared_commands::destroy_service(server, service) ? 0 : 1);
}
}

View File

@ -141,7 +141,7 @@ int help_handler(const CommandContext& ctx) {
info << std::endl;
show_command("install");
show_command("uninstall");
show_command("nuke");
show_command("destroy");
info << std::endl;
show_command("start");
show_command("stop");

View File

@ -175,9 +175,9 @@ namespace dropshell
info << "Backup complete." << std::endl;
}
{ // nuke the old service
info << "2) Nuking old service..." << std::endl;
if (!shared_commands::nuke_service(server, service))
{ // Destroy the old service
info << "2) Destroying old service..." << std::endl;
if (!shared_commands::destroy_service(server, service))
return 1;
}

View File

@ -90,8 +90,8 @@ namespace dropshell
// defined in uninstall.cpp
bool uninstall_service(const ServerConfig &server_env, const std::string &service);
// defined in nuke.cpp
bool nuke_service(const std::string &server, const std::string &service);
// defined in destroy.cpp
bool destroy_service(const std::string &server, const std::string &service);
// defined in install.cpp
bool install_service(const ServerConfig &server_env, const std::string &service);

View File

@ -36,7 +36,7 @@ namespace dropshell
uninstall SERVER SERVICE Uninstall the given service on the given server.
uninstall SERVER all Uninstall all services on the given server.
Update and reinstall the service with install, or delete all configuration and data with nuke.
Update and reinstall the service with install, or delete all configuration and data with destroy.
)"});
}
} uninstall_command_register;

View File

@ -140,115 +140,6 @@ auto command_match = [](const std::string& cmd_list, int argc, char* argv[]) ->
} \
}
// int old_main(int argc, char* argv[]) {
// HAPPYEXIT("hash", hash_demo_raw(safearg(argc,argv,2)))
// HAPPYEXIT("version", printversion())
// BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2)))
// ASSERT(safearg(argc,argv,1) != "assert", "Hello! Here is an assert.");
// try {
// // silently attempt to load the config file and templates.
// gConfig().load_config();
// if (gConfig().is_config_set())
// gTemplateManager().load_sources();
// std::string cmd = argv[1];
// // ------------------------------------------------------------
// // from here we require the config file to be loaded.
// if (!gConfig().is_config_set())
// return die("Please run 'dropshell edit' to set up the dropshell configuration.");
// const std::vector<std::string> & server_definition_paths = gConfig().get_local_server_definition_paths();
// if (server_definition_paths.size()>1) { // only show if there are multiple.
// std::cout << "Server definition paths: ";
// for (auto & dir : server_definition_paths)
// std::cout << "["<< dir << "] ";
// std::cout << std::endl;
// }
// if (gTemplateManager().is_loaded() && gTemplateManager().get_source_count() > 0)
// gTemplateManager().print_sources();
// HAPPYEXIT("templates", gTemplateManager().list_templates());
// if (cmd == "create-template") {
// if (argc < 3) return die("Error: create-template requires a template name");
// return (gTemplateManager().create_template(argv[2])) ? 0 : 1;
// }
// if (cmd == "create-server") {
// if (argc < 3) return die("Error: create-server requires a server name");
// return (create_server(argv[2])) ? 0 : 1;
// }
// if (cmd == "create-service") {
// if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service");
// return (create_service(argv[2], argv[3], argv[4])) ? 0 : 1;
// }
// if (cmd == "ssh" && argc < 4) {
// if (argc < 3) return die("Error: ssh requires a server name and optionally service name");
// service_runner::interactive_ssh(argv[2], "bash");
// return 0;
// }
// // handle running a command.
// std::set<std::string> commands;
// get_all_used_commands(commands);
// autocomplete::merge_commands(commands, autocomplete::service_commands_require_config); // handled by service_runner, but not in template_shell_commands.
// if (commands.count(cmd)) {
// std::set<std::string> safe_commands = {"nuke", "fullnuke"};
// if (safe_commands.count(cmd) && argc < 4)
// return die("Error: "+cmd+" requires a server name and service name. For safety, can't run on all services.");
// // get all the services to run the command on.
// ServerAndServices server_and_services;
// if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services))
// return die("Error: "+cmd+" command requires server name and optionally service name");
// // run the command on each service.
// for (const auto& service_info : server_and_services.servicelist) {
// if (!SIvalid(service_info))
// std::cerr<<"Error: Unable to get service information."<<std::endl;
// else {
// service_runner runner(server_and_services.server_name, service_info.service_name);
// if (!runner.isValid())
// return die("Error: Failed to initialize service");
// std::vector<std::string> additional_args;
// for (int i=4; i<argc; i++)
// additional_args.push_back(argv[i]);
// if (!runner.run_command(cmd, additional_args))
// return die(cmd+" failed on service "+service_info.service_name);
// }
// }
// // success!
// return 0;
// }
// // Unknown command
// std::cerr << "Error: Unknown command '" << cmd << "'" << std::endl;
// std::cerr << "Valid commands: ";
// for (const auto& command : commands) {
// if (!command.empty() && command[0]!='_')
// std::cerr << command << " ";
// }
// std::cerr << std::endl;
// return 1;
// } catch (const std::exception& e) {
// std::cerr << "Error: " << e.what() << std::endl;
// return 1;
// }
// }
} // namespace dropshell
int main(int argc, char* argv[]) {

View File

@ -19,74 +19,6 @@
// #include "shared_commands.hpp"
// namespace dropshell {
// class service_runner {
// public:
// service_runner(const std::string& server_name, const std::string& service_name);
// bool isValid() const { return mValid; }
// // run a command over ssh, using the credentials from server.env (via server_env.hpp)
// // first check that the command corresponds to a valid .sh file in the service directory
// // then run the command, passing the {service_name}.env file as an argument
// // do a lot of checks, such as:
// // checking that we can ssh to the server.
// // checking whether the service directory exists on the server.
// // checking that the command exists in the service directory.
// // checking that the command is a valid .sh file.
// // checking that the {service_name}.env file exists in the service directory.
// bool run_command(const std::string& command, std::vector<std::string> additional_args={}, std::map<std::string, std::string> env_vars={});
// // check health of service. Silent.
// // 1. run status.sh on the server
// // 2. return the output of the status.sh script
// //HealthStatus is_healthy();
// // std::string healthtick();
// // std::string healthmark();
// public:
// // backup and restore
// bool backup(bool silent=false);
// bool restore(std::string backup_file, bool silent=false);
// // nuke the service
// bool nuke(bool silent=false); // nukes all data for this service on the remote server
// bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles
// // launch an interactive ssh session on a server or service
// // replaces the current dropshell process with the ssh process
// bool interactive_ssh_service();
// bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false);
// bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false);
// public:
// // utility functions
// static std::string get_latest_backup_file(const std::string& server, const std::string& service);
// static bool interactive_ssh(const std::string & server_name, const std::string & command);
// // static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
// private:
// std::string mServer;
// ServerConfig mServerEnv;
// LocalServiceInfo mServiceInfo;
// std::string mService;
// bool mValid;
// // Helper methods
// public:
// };
// } // namespace dropshell
// namespace fs = std::filesystem;
// namespace dropshell {
@ -113,47 +45,6 @@
// bool service_runner::nuke(bool silent)
// {
// maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);
// if (!mServerEnv.is_valid()) return false; // should never hit this.
// std::string remote_service_path = remotepath::service(mServer, mService);
// info << "Service " << mService << " successfully nuked from " << mServer << std::endl;
// if (!silent) {
// info << "There's nothing left on the remote server." << std::endl;
// info << "You can remove the local files with:" << std::endl;
// info << " rm -rf " << localpath::service(mServer,mService) << std::endl;
// }
// return true;
// }
// bool service_runner::fullnuke()
// {
// if (!nuke(true))
// {
// warning << "Nuke script failed, aborting." << std::endl;
// return false;
// }
// std::string local_service_path = mServiceInfo.local_service_path;
// if (local_service_path.empty() || !fs::exists(local_service_path)) {
// error << "Service directory not found: " << local_service_path << std::endl;
// return false;
// }
// std::string rm_cmd = "rm -rf " + quote(local_service_path);
// if (!execute_local_command("", rm_cmd, {}, nullptr, cMode::Silent)) {
// error << "Failed to remove service directory" << std::endl;
// return false;
// }
// return true;
// }
// // ------------------------------------------------------------------------------------------------
// // Run a command on the service.
@ -169,15 +60,6 @@
// return false;
// }
// if (command == "fullnuke")
// return fullnuke();
// if (command == "nuke")
// {
// std::cout << "Nuking " << mService << " (" << mServiceInfo.template_name << ") on " << mServer << std::endl;
// return nuke();
// }
// if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) {
// std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
// return true; // nothing to run.
@ -232,298 +114,4 @@
// }
// bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) {
// std::string serverpath = localpath::server(server_name);
// if (serverpath.empty()) {
// std::cerr << "Error: Server not found: " << server_name << std::endl;
// return false;
// }
// ServerConfig env(server_name);
// if (!env.is_valid()) {
// std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
// return false;
// }
// sCommand scommand("", "bash",{});
// return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
// }
// bool service_runner::interactive_ssh_service()
// {
// std::set<std::string> used_commands = get_used_commands(mServer, mService);
// if (used_commands.find("ssh") == used_commands.end()) {
// std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
// return false;
// }
// std::vector<std::string> args; // not passed through yet.
// return mServerEnv.run_remote_template_command(mService, "ssh", args, false, {});
// }
// bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent)
// {
// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : "");
// return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
// }
// bool service_runner::scp_file_from_remote(const std::string &remote_path, const std::string &local_path, bool silent)
// {
// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.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));
// }
// bool service_runner::restore(std::string backup_file, bool silent)
// {
// if (backup_file.empty()) {
// std::cerr << "Error: not enough arguments. dropshell restore <server> <service> <backup-file>" << std::endl;
// return false;
// }
// std::string local_backups_dir = gConfig().get_local_backup_path();
// if (backup_file == "latest") {
// // get the latest backup file from the server
// backup_file = get_latest_backup_file(mServer, mService);
// }
// std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string();
// if (! std::filesystem::exists(local_backup_file_path)) {
// std::cerr << "Error: Backup file not found at " << local_backup_file_path << std::endl;
// return false;
// }
// // split the backup filename into parts based on the magic string
// std::vector<std::string> parts = dropshell::split(backup_file, "-_-");
// if (parts.size() != 4) {
// std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
// return false;
// }
// std::string backup_server_name = parts[0];
// std::string backup_template_name = parts[1];
// std::string backup_service_name = parts[2];
// std::string backup_datetime = parts[3];
// if (backup_template_name != mServiceInfo.template_name) {
// std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl;
// return false;
// }
// std::string nicedate = std::string(backup_datetime).substr(0, 10);
// std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<<backup_server_name<<", onto "<<mServer<<"/"<<mService<<std::endl;
// std::cout << std::endl;
// std::cout << "*** ALL DATA FOR "<<mServer<<"/"<<mService<<" WILL BE OVERWRITTEN! ***"<<std::endl;
// // run the restore script
// std::cout << "OK, here goes..." << std::endl;
// { // backup existing service
// maketitle("1) Backing up old service... ");
// if (!backup(true)) // silent=true
// {
// std::cerr << std::endl;
// std::cerr << "Error: Backup failed, restore aborted." << std::endl;
// std::cerr << "You can try using dropshell install "<<mServer<<" "<<mService<<" to install the service afresh." << std::endl;
// std::cerr << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl;
// return false;
// }
// std::cout << "Backup complete." << std::endl;
// }
// { // uninstall service, then nuke it.
// maketitle("2) Uninstalling old service...");
// // if (!uninstall(true))
// // return false;
// maketitle("3) Nuking old service...");
// // if (!nuke(true))
// // return false;
// }
// { // restore service from backup
// maketitle("4) Restoring service data from backup...");
// std::string remote_backups_dir = remotepath::backups(mServer);
// std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// // Copy backup file from local to server
// if (!scp_file_to_remote(local_backup_file_path, remote_backup_file_path, silent)) {
// std::cerr << "Failed to copy backup file from local to server" << std::endl;
// return false;
// }
// shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
// mServerEnv.run_remote_template_command(mService, "restore", {}, silent, {{"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
// // maketitle("5) Non-destructive install of fresh service...");
// // if (!install_service(mServer, mService, true))
// // return false;
// // }
// bool healthy = false;
// {// healthcheck the service
// maketitle("6) Healthchecking service...");
// std::string green_tick = "\033[32m✓\033[0m";
// std::string red_cross = "\033[31m✗\033[0m";
// healthy= (mServerEnv.run_remote_template_command(mService, "status", {}, silent, {}));
// if (!silent)
// std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
// }
// return healthy;
// }
// // backup the service over ssh, using the credentials from server.env (via server_env.hpp)
// // 1. run backup.sh on the server
// // 2. create a backup file with format server-service-datetime.tgz
// // 3. store it in the server's DROPSHELL_DIR/backups folder
// // 4. copy it to the local user_dir/backups folder
// // ------------------------------------------------------------------------------------------------
// // Backup the service.
// // ------------------------------------------------------------------------------------------------
// bool service_runner::backup(bool silent) {
// auto service_info = get_service_info(mServer, mService);
// if (service_info.local_service_path.empty()) {
// std::cerr << "Error: Service not found" << std::endl;
// return 1;
// }
// const std::string command = "backup";
// if (!gTemplateManager().template_command_exists(service_info.template_name, command)) {
// std::cout << "No backup script for " << service_info.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(mServer, mService);
// std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
// std::string remote_service_config_path = remotepath::service_config(mServer, mService);
// if (!mServerEnv.check_remote_items_exist({
// remotepath::service(mServer, mService),
// remote_command_script_file,
// remotefile::service_env(mServer, mService)})
// )
// {
// std::cerr << "Error: Required service directories not found on remote server" << std::endl;
// std::cerr << "Is the service installed?" << std::endl;
// return false;
// }
// // Create backups directory on server if it doesn't exist
// std::string remote_backups_dir = remotepath::backups(mServer);
// if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
// std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
// if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Silent)) {
// std::cerr << "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()) {
// std::cerr << "Error: Local backups directory not found" << std::endl;
// std::cerr << "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
// auto now = std::chrono::system_clock::now();
// auto time = std::chrono::system_clock::to_time_t(now);
// std::stringstream datetime;
// datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
// // Construct backup filename
// shared_commands::cBackupFileName backup_filename_construction(mServer, mService, service_info.template_name);
// if (!backup_filename_construction.is_valid()) {
// std::cerr << "Invalid backup filename" << std::endl;
// return false;
// }
// std::string backup_filename = backup_filename_construction.get_filename();
// std::string remote_backup_file_path = remote_backups_dir + "/" + backup_filename;
// std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string();
// // assert that the backup filename is valid - -_- appears exactly 3 times in local_backup_file_path.
// ASSERT(3 == count_substring("-_-", local_backup_file_path), "Invalid backup filename");
// { // Run backup script
// shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
// if (!mServerEnv.run_remote_template_command(mService, command, {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) {
// std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl;
// return false;
// }
// // Copy backup file from server to local
// if (!scp_file_from_remote(remote_backup_file_path, local_backup_file_path, silent)) {
// std::cerr << "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
// if (!silent) {
// std::cout << "Backup created successfully. Restore with:"<<std::endl;
// std::cout << " dropshell restore " << mServer << " " << mService << " " << backup_filename << std::endl;
// }
// return true;
// }
// // Helper function to get the latest backup file for a given server and service
// std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) {
// std::string local_backups_dir = gConfig().get_local_backup_path();
// if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
// std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
// return "";
// }
// // Get the template name for this service
// LocalServiceInfo info = get_service_info(server, service);
// if (info.template_name.empty()) {
// std::cerr << "Error: Could not determine template name for service: " << service << std::endl;
// return "";
// }
// // Build the expected prefix for backup files
// std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-";
// std::string latest_file;
// std::string latest_datetime;
// std::cout << "Looking for backup files in " << local_backups_dir << std::endl;
// for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
// if (!entry.is_regular_file()) continue;
// std::string filename = entry.path().filename().string();
// if (filename.rfind(prefix, 0) == 0) { // starts with prefix
// // Extract the datetime part
// size_t dt_start = prefix.size();
// size_t dt_end = filename.find(".tgz", dt_start);
// if (dt_end == std::string::npos) continue;
// std::string datetime = filename.substr(dt_start, dt_end - dt_start);
// std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl;
// if (datetime > latest_datetime) {
// latest_datetime = datetime;
// latest_file = filename;
// }
// }
// }
// if (latest_file.empty()) {
// std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl;
// }
// std::cout << "Latest backup file: " << latest_file << std::endl;
// return latest_file;
// }
// } // namespace dropshell