From a27dd7638f2d90b5748381eb6eb5a058e07e9a99 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 11 May 2025 21:05:17 +1200 Subject: [PATCH] Nuke and uninstall. --- src/commands/list.cpp | 79 ++++++++++++++++++++++ src/commands/nuke.cpp | 96 +++++++++++++++++++++++++++ src/commands/shared_commands.hpp | 1 + src/commands/uninstall.cpp | 109 +++++++++++++++++++++++++++++++ src/main.cpp | 24 ------- src/servers.cpp | 69 ------------------- src/servers.hpp | 4 -- 7 files changed, 285 insertions(+), 97 deletions(-) create mode 100644 src/commands/nuke.cpp create mode 100644 src/commands/uninstall.cpp diff --git a/src/commands/list.cpp b/src/commands/list.cpp index 34057c6..64b0684 100644 --- a/src/commands/list.cpp +++ b/src/commands/list.cpp @@ -17,6 +17,8 @@ namespace dropshell { int list_handler(const CommandContext& ctx); +void show_server_details(const std::string& server_name); +void list_servers(); static std::vector list_name_list={"list","ls","info","-l"}; @@ -53,6 +55,11 @@ int list_handler(const CommandContext& ctx) { return 0; } + if (ctx.args.size() == 1) { + show_server_details(ctx.args[0]); + return 0; + } + std::cout << "List handler called with " << ctx.args.size() << " args\n"; return 0; } @@ -101,4 +108,76 @@ void list_servers() { } + + +void show_server_details(const std::string& server_name) { + server_env_manager env(server_name); + if (!env.is_valid()) { + std::cerr << "Error: Invalid server environment file: " << server_name << std::endl; + return; + } + + //--------------------- + // Check if server is reachable via SSH + std::string ssh_address = env.get_SSH_HOST(); + std::string ssh_user = env.get_SSH_USER(); + std::string ssh_port = env.get_SSH_PORT(); + if (!ssh_address.empty()) { + std::cout << std::endl << "Server Status:" << std::endl; + std::cout << std::string(40, '-') << std::endl; + + // Try to connect to the server + std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " 'echo connected' 2>/dev/null"; + int result = system(cmd.c_str()); + if (result == 0) { + std::cout << "Status: Online" << std::endl; + + // // Get uptime if possible + // cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null"; + // int rval = system(cmd.c_str()); + // if (rval != 0) { + // std::cout << "Error: Failed to get uptime" << std::endl; + // } + } else { + std::cout << "Status: Offline" << std::endl; + } + } + std::cout << std::endl; + + //--------------------- + { + std::cout << std::endl; + tableprint tp("Server Configuration: " + server_name, true); + tp.add_row({"Key", "Value"}); + for (const auto& [key, value] : env.get_variables()) { + tp.add_row({key, value}); + } + tp.print(); + } + + //--------------------- + // list services, and run healthcheck on each + { + tableprint tp("Services: " + server_name, false); + tp.add_row({"Status", "Service", "Ports"}); + + + std::map status = service_runner::get_all_services_status(server_name); + + std::set ports_used; + std::string serviceticks = ""; + for (const auto& [service_name, service_status] : status) { + std::string healthy = service_runner::HealthStatus2String(service_status.health); + + std::string ports_str = ""; + for (const auto& port : service_status.ports) + ports_str += std::to_string(port) + " "; + + tp.add_row({healthy, service_name, ports_str}); + } // end of for (const auto& service : services) + tp.print(); + } // end of list services +} // end of show_server_details + + } // namespace dropshell \ No newline at end of file diff --git a/src/commands/nuke.cpp b/src/commands/nuke.cpp new file mode 100644 index 0000000..1426e4f --- /dev/null +++ b/src/commands/nuke.cpp @@ -0,0 +1,96 @@ +#include "command_registry.hpp" +#include "shared_commands.hpp" +#include "config.hpp" +#include "services.hpp" +#include "server_env_manager.hpp" +#include "utils/directories.hpp" +#include "servers.hpp" +#include "templates.hpp" + +#include + +namespace dropshell { + +int nuke_handler(const CommandContext& ctx); +static std::vector nuke_name_list={"nuke"}; + +// Static registration +struct NukeCommandRegister { + NukeCommandRegister() { + CommandRegistry::instance().register_command({ + nuke_name_list, + nuke_handler, + std_autocomplete, + false, // hidden + true, // requires_config + 2, // min_args (after command) + 2, // max_args (after command) + "nuke SERVER SERVICE", + "Nuke a service on a server. Destroys everything, both local and remote!", + // heredoc + R"( + Nuke a service on a server. Destroys everything, both local and remote! + nuke nuke the given service on the given server. + )" + }); + } +} nuke_command_register; + +int nuke_handler(const CommandContext &ctx) +{ + ASSERT(ctx.args.size() > 1, "Usage: nuke "); + 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); + + maketitle("Nuking " + service + " on " + server); + + server_env_manager server_env(server); + LocalServiceInfo service_info; + + if (server_env.is_valid()) + { + service_info = get_service_info(server, service); + if (!SIvalid(service_info)) + std::cerr << "Warning: Invalid service: " << service << std::endl; + else + if (!uninstall_service(server, service, false)) + std::cerr << "Warning: Failed to uninstall service: " << service << std::endl; + + // run the nuke script on the remote server if it exists. + if (gTemplateManager().template_command_exists(service_info.template_name, "nuke")) + { + if (!server_env.run_remote_template_command(service, "nuke", {}, false, {})) + std::cerr << "Warning: Failed to run nuke script: " << service << std::endl; + } + + // remove the remote service directory, running in a docker container as root. + std::string rm_cmd = "docker run --rm -v " + quote(remotepath::service(server, service)) + ":/service alpine rm -rf /service"; + if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand(rm_cmd), cMode::Silent)) + { + std::cerr << "Warning: Failed to remove remote service directory" << std::endl; + std::cerr << "You may need to log in and delete it manually." << std::endl; + std::cerr << " ssh " << server << std::endl; + std::cerr << " rm -rf " << remotepath::service(server, service) << std::endl; + } + } + else + std::cerr << "Warning: Invalid server: " << server << std::endl; + + // remove the local service directory + std::string local_service_path = service_info.local_service_path; + if (local_service_path.empty() || !std::filesystem::exists(local_service_path)) + { + std::cerr << "Warning: Local service directory not found: " << local_service_path << std::endl; + return 1; + } + + auto ret = std::filesystem::remove_all(local_service_path); + if (ret != 0) + std::cerr << "Warning: Failed to remove local service directory" << std::endl; + + return ret == 0 ? 0 : 1; +} + +} // namespace dropshell \ No newline at end of file diff --git a/src/commands/shared_commands.hpp b/src/commands/shared_commands.hpp index d07dc6a..4e71c91 100644 --- a/src/commands/shared_commands.hpp +++ b/src/commands/shared_commands.hpp @@ -15,6 +15,7 @@ namespace dropshell { // defined in install.cpp bool install_service(const std::string& server, const std::string& service, bool silent); + bool uninstall_service(const std::string& server, const std::string& service, bool silent); // defined in health.cpp std::string healthtick(const std::string& server, const std::string& service); diff --git a/src/commands/uninstall.cpp b/src/commands/uninstall.cpp new file mode 100644 index 0000000..7f09731 --- /dev/null +++ b/src/commands/uninstall.cpp @@ -0,0 +1,109 @@ +#include "command_registry.hpp" +#include "directories.hpp" +#include "shared_commands.hpp" + +namespace dropshell +{ + + int uninstall_handler(const CommandContext &ctx); + + static std::vector uninstall_name_list = {"uninstall", "remove"}; + + // Static registration + struct UninstallCommandRegister + { + UninstallCommandRegister() + { + CommandRegistry::instance().register_command({uninstall_name_list, + uninstall_handler, + std_autocomplete, + false, // hidden + false, // requires_config + 1, // min_args (after command) + 2, // max_args (after command) + "uninstall SERVER [SERVICE]", + "Uninstall a service on a server. Does not remove configuration or user data.", + // heredoc + R"( + Uninstall a service on a server. Does not remove configuration or user data. + uninstall uninstall the given service on the given server. + uninstall uninstall all services on the given server. + )"}); + } + } uninstall_command_register; + + int uninstall_handler(const CommandContext &ctx) + { + if (ctx.args.size() < 1) + { + std::cerr << "Error: uninstall requires a server and (optionally) a service" << std::endl; + return 1; + } + + std::string server = safearg(ctx.args, 0); + + if (ctx.args.size() == 1) + { + // uninstall all services on the server + bool okay = true; + std::vector services = get_server_services_info(server); + for (const auto &service : services) + { + if (!uninstall_service(server, service.service_name, false)) + okay = false; + } + return okay ? 0 : 1; + } + + std::string service = safearg(ctx.args, 1); + return uninstall_service(server, service, false) ? 0 : 1; + } + + bool uninstall_service(const std::string &server, const std::string &service, bool silent) + { + if (!silent) + maketitle("Uninstalling " + service + " on " + server); + + server_env_manager server_env(server); + if (!server_env.is_valid()) + { + std::cerr << "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))) + { + std::cerr << "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"; + bool script_exists = server_env.check_remote_file_exists(uninstall_script); + + if (script_exists) + { + if (!server_env.run_remote_template_command(service, "uninstall", {}, silent, {})) + if (!silent) + std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl; + } + else + if (!silent) + std::cerr << "Warning: No uninstall script found, continuing with direcotry removal." << std::endl; + + // 4. Remove the service directory from the server + std::string rm_cmd = "rm -rf " + quote(remotepath::service(server, service)); + if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand(rm_cmd), cMode::Silent)) + { + if (!silent) + std::cerr << "Failed to remove service directory" << std::endl; + return false; + } + + if (!silent) + std::cout << "Service " << service << " successfully uninstalled from " << server << std::endl; + return true; + } + +} // namespace dropshell \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 668ee6e..d29ff02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -158,14 +158,6 @@ int old_main(int argc, char* argv[]) { std::string cmd = argv[1]; - if (cmd == "autocomplete") - { - std::vector argvec; - for (int i = 0; i < argc; i++) - argvec.push_back(argv[i]); - return autocomplete::autocomplete(argvec) ? 0 : 1; - } - // ------------------------------------------------------------ // from here we require the config file to be loaded. @@ -183,22 +175,6 @@ int old_main(int argc, char* argv[]) { if (gTemplateManager().is_loaded() && gTemplateManager().get_source_count() > 0) gTemplateManager().print_sources(); - if (cmd == "server" || cmd == "servers" || cmd == "list" || cmd == "view") - switch (argc) - { - case 2: - list_servers(); - return 0; - case 3: - show_server_details(argv[2]); - return 0; - case 4: - cmd="logs"; - break; - default: - return die("dropshell server: too many arguments"); - } - HAPPYEXIT("templates", gTemplateManager().list_templates()); if (cmd == "create-template") { diff --git a/src/servers.cpp b/src/servers.cpp index c477d65..5564bfc 100644 --- a/src/servers.cpp +++ b/src/servers.cpp @@ -72,75 +72,6 @@ ServerInfo get_server_info(const std::string &server_name) } -void show_server_details(const std::string& server_name) { - server_env_manager env(server_name); - if (!env.is_valid()) { - std::cerr << "Error: Invalid server environment file: " << server_name << std::endl; - return; - } - - //--------------------- - // Check if server is reachable via SSH - std::string ssh_address = env.get_SSH_HOST(); - std::string ssh_user = env.get_SSH_USER(); - std::string ssh_port = env.get_SSH_PORT(); - if (!ssh_address.empty()) { - std::cout << std::endl << "Server Status:" << std::endl; - std::cout << std::string(40, '-') << std::endl; - - // Try to connect to the server - std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " 'echo connected' 2>/dev/null"; - int result = system(cmd.c_str()); - if (result == 0) { - std::cout << "Status: Online" << std::endl; - - // // Get uptime if possible - // cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null"; - // int rval = system(cmd.c_str()); - // if (rval != 0) { - // std::cout << "Error: Failed to get uptime" << std::endl; - // } - } else { - std::cout << "Status: Offline" << std::endl; - } - } - std::cout << std::endl; - - //--------------------- - { - std::cout << std::endl; - tableprint tp("Server Configuration: " + server_name, true); - tp.add_row({"Key", "Value"}); - for (const auto& [key, value] : env.get_variables()) { - tp.add_row({key, value}); - } - tp.print(); - } - - //--------------------- - // list services, and run healthcheck on each - { - tableprint tp("Services: " + server_name, false); - tp.add_row({"Status", "Service", "Ports"}); - - - std::map status = service_runner::get_all_services_status(server_name); - - std::set ports_used; - std::string serviceticks = ""; - for (const auto& [service_name, service_status] : status) { - std::string healthy = service_runner::HealthStatus2String(service_status.health); - - std::string ports_str = ""; - for (const auto& port : service_status.ports) - ports_str += std::to_string(port) + " "; - - tp.add_row({healthy, service_name, ports_str}); - } // end of for (const auto& service : services) - tp.print(); - } // end of list services -} // end of show_server_details - bool create_server(const std::string &server_name) { // 1. check if server name already exists diff --git a/src/servers.hpp b/src/servers.hpp index 4afbf66..eb09849 100644 --- a/src/servers.hpp +++ b/src/servers.hpp @@ -20,10 +20,6 @@ namespace dropshell { ServerInfo get_server_info(const std::string& server_name); - - void list_servers(); - void show_server_details(const std::string& server_name); - bool create_server(const std::string& server_name); void get_all_used_commands(std::set &commands);