#include "main_commands.hpp" #include "utils/directories.hpp" #include "utils/utils.hpp" #include "utils/readmes.hpp" #include "service_runner.hpp" #include "config.hpp" #include "templates.hpp" #include "server_env_manager.hpp" #include #include #include namespace dropshell { namespace main_commands { static const std::string magic_string = "-_-"; int init(const std::vector &args) { std::string lcd; if (args.size() < 3) { std::cerr << "Error: init command requires a directory argument" << std::endl; return 1; } try { if (!gConfig().add_local_config_directory(args[2])) return 1; // error already reported gConfig().save_config(); std::cout << "Config directory added: " << gConfig().get_local_config_directories().back() << std::endl; dropshell::create_readme_local_config_dir(gConfig().get_local_config_directories().back()); if (gConfig().get_local_config_directories().size() ==1) std::cout << "DropShell is now initialised and you can add a server with 'dropshell create-server '" << std::endl; else { std::cout << "DropShell will now use all of the following directories for configuration:" << std::endl; for (const auto& dir : gConfig().get_local_config_directories()) { std::cout << " " << dir << std::endl; } std::cout << "You can edit the config file manually with: dropshell edit" << std::endl; } } catch (const std::exception& e) { std::cerr << "Error in init: " << e.what() << std::endl; return 1; } return 0; } int restore(const std::vector &args, bool silent) { if (args.size() < 4) { std::cerr << "Error: not enough arguments. dropshell restore " << std::endl; return 1; } std::string server_name = args[2]; std::string service_name = args[3]; std::string backup_file = args[4]; auto service_info = get_service_info(server_name, service_name); if (service_info.local_service_path.empty()) { std::cerr << "Error: Service not found" << std::endl; return 1; } server_env_manager env(server_name); if (!env.is_valid()) { std::cerr << "Error: Invalid server environment" << std::endl; return 1; } std::string local_backups_dir = localpath::backups_path(); 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 1; } // split the backup filename into parts based on the magic string std::vector parts = dropshell::split(backup_file, magic_string); if (parts.size() != 4) { std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl; return 1; } 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 != service_info.template_name) { std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl; return 1; } std::string nicedate = std::string(backup_datetime).substr(0, 10); std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<> confirm; if (confirm != 'y') { std::cout << "Restore cancelled." << std::endl; return 1; } // run the restore script std::cout << "OK, here goes..." << std::endl; { // backup existing service std::cout << "1) Backing up existing service... " << std::flush; std::vector backup_args = {"dropshell","backup",server_name, service_name}; if (!backup(backup_args,true)) // silent=true { std::cerr << std::endl; std::cerr << "Error: Backup failed, restore aborted." << std::endl; std::cerr << "You can try using dropshell install "< /dev/null 2>&1" : ""); if (!env.execute_local_command(scp_cmd)) { std::cerr << "Failed to copy backup file from server" << std::endl; return false; } env.run_remote_template_command(service_name, "restore", {remote_backup_file_path}, silent); } // healthcheck the service std::cout << "3) Healthchecking service..." << std::endl; std::string green_tick = "\033[32m✓\033[0m"; std::string red_cross = "\033[31m✗\033[0m"; bool healthy= (env.run_remote_template_command(service_name, "status", {}, silent)); if (!silent) std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl; return 0; } 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) // 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. // ------------------------------------------------------------------------------------------------ int backup(const std::vector & args, bool silent) { if (args.size() < 4) { std::cerr << "Error: backup command requires a server name and service name" << std::endl; return 1; } std::string server_name = args[2]; std::string service_name = args[3]; auto service_info = get_service_info(server_name, service_name); if (service_info.local_service_path.empty()) { std::cerr << "Error: Service not found" << std::endl; return 1; } server_env_manager env(server_name); if (!env.is_valid()) { std::cerr << "Error: Invalid server environment" << std::endl; return 1; } const std::string command = "backup"; if (!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(server_name, service_name); std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh"; std::string remote_service_config_path = remotepath::service_config(server_name, service_name); if (!env.check_remote_items_exist({ remotepath::service(server_name, service_name), remote_command_script_file, remotefile::service_env(server_name, service_name)}) ) { 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(server_name); if (!silent) std::cout << "Remote backups directory on "<< server_name <<": " << remote_backups_dir << std::endl; std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir); if (!env.execute_ssh_command(mkdir_cmd)) { 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 = localpath::backups_path(); if (local_backups_dir.empty()) { std::cerr << "Error: Local backups directory not found - is DropShell initialised?" << 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"); if (name_breaks_backups(server_name)) {std::cerr << "Error: Server name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;} if (name_breaks_backups(service_name)) {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 std::string backup_filename = server_name + magic_string + service_info.template_name + magic_string + service_name + magic_string + datetime.str() + ".tgz"; 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)); // Run backup script if (!env.run_remote_template_command(service_name, command, {remote_backup_file_path}, silent)) { std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; return false; } // Copy backup file from server to local std::string scp_cmd = "scp -P " + env.get_SSH_PORT() + " " + env.get_SSH_USER() + "@" + env.get_SSH_HOST() + ":" + quote(remote_backup_file_path) + " " + quote(local_backup_file_path) + (silent ? " > /dev/null 2>&1" : ""); if (!env.execute_local_command(scp_cmd)) { std::cerr << "Failed to copy backup file from server" << std::endl; return false; } if (!silent) { std::cout << "Backup created successfully. Restore with:"<