#include #include #include #include #include #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 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"( 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; namespace shared_commands { 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. } std::string user = server_env.get_user_for_service(server, service); // Check if basic installed stuff is in place. std::string remote_service_template_path = remotepath(server, user).service_template(service); std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh"; std::string remote_service_config_path = remotepath(server, user).service_config(service); if (!server_env.check_remote_items_exist({remotepath(server, user).service(service), remote_command_script_file, remotefile(server, user).service_env(service)}, user)) { 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(server, user).backups(); 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(user), 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 = localpath::backups(); 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, user); 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) { ASSERT(ctx.args.size() == 2, "Invalid number of arguments"); std::string server = safearg(ctx.args, 0); std::string service = safearg(ctx.args, 1); if (service == "all") { // backup all services on the server maketitle("Backing up data for all services on " + server); bool okay = true; std::vector services = get_server_services_info(server); for (const LocalServiceInfo &si : services) okay &= shared_commands::backupdata_service(server, si.service_name); return okay ? 0 : 1; } return shared_commands::backupdata_service(server, service) ? 0 : 1; } } // namespace dropshell