#include #include #include #include #include #include #include #include #include "utils/assert.hpp" #include "config.hpp" #include "server_env_manager.hpp" #include "templates.hpp" #include "services.hpp" #include "utils/directories.hpp" #include "utils/utils.hpp" #include "command_registry.hpp" #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 additional_args={}, std::map 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 get_all_services_status(std::string server_name); private: std::string mServer; server_env_manager mServerEnv; LocalServiceInfo mServiceInfo; std::string mService; bool mValid; // Helper methods public: }; } // namespace dropshell namespace fs = std::filesystem; namespace dropshell { 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) { if (server_name.empty() || service_name.empty()) return; // Initialize server environment if (!mServerEnv.is_valid()) return; mServiceInfo = get_service_info(server_name, service_name); if (mServiceInfo.service_name.empty()) return; mService = mServiceInfo.service_name; mValid = !mServiceInfo.local_template_path.empty(); } 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. // ------------------------------------------------------------------------------------------------ bool service_runner::run_command(const std::string& command, std::vector additional_args, std::map env_vars) { if (!mServerEnv.is_valid()) { std::cerr << "Error: Server service not initialized" << std::endl; return false; } template_info tinfo = gTemplateManager().get_template_info(mServiceInfo.template_name); if (!tinfo.is_set()) { std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl; 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. } // install doesn't require anything on the server yet. // if (command == "install") // return install_service(mServer, mService, false); std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh"; // Check if service directory exists if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) { std::cerr << "Error: Service is not installed: " << mService << std::endl; return false; } // Check if command script exists if (!mServerEnv.check_remote_file_exists(script_path)) { std::cerr << "Error: Remote command script not found: " << script_path << std::endl; return false; } // Check if env file exists if (!mServerEnv.check_remote_file_exists(remotefile::service_env(mServer, mService))) { std::cerr << "Error: Service config file not found: " << remotefile::service_env(mServer, mService) << std::endl; return false; } // if (command == "uninstall") // return uninstall(); if (command == "ssh") { interactive_ssh_service(); return true; } if (command == "restore") { if (additional_args.size() < 1) { std::cerr << "Error: restore requires a backup file:" << std::endl; std::cerr << "dropshell restore " << std::endl; return false; } return restore(additional_args[0], false); } if (command == "backup") { return backup(false); } // Run the generic command std::vector args; // not passed through yet. return mServerEnv.run_remote_template_command(mService, command, args, false, env_vars); } 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; } server_env_manager 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 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 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_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_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 " << 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 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 "< 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