Refact0r
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled

This commit is contained in:
Your Name
2025-05-17 19:40:51 +12:00
parent 203068048d
commit 1d3bb634f0
17 changed files with 608 additions and 499 deletions

View File

@ -0,0 +1,133 @@
#include "command_registry.hpp"
#include "directories.hpp"
#include "shared_commands.hpp"
#include "templates.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "services.hpp"
namespace dropshell
{
int create_service_handler(const CommandContext &ctx);
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent);
void create_service_autocomplete(const CommandContext &ctx);
static std::vector<std::string> create_service_name_list = {"create-service"};
// Static registration
struct UninstallCommandRegister
{
UninstallCommandRegister()
{
CommandRegistry::instance().register_command({create_service_name_list,
create_service_handler,
create_service_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
2, // min_args (after command)
2, // max_args (after command)
"create-service SERVER SERVICE TEMPLATE",
"Create a service on a server.",
// heredoc
R"(
Create a service on a server.
create-service SERVER SERVICE TEMPLATE create the given service on the given server.
)"});
}
} create_service_command_register;
int create_service_handler(const CommandContext &ctx)
{
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
std::string template_name = safearg(ctx.args, 2);
return create_service(server, template_name, service, false) ? 0 : 1;
}
void create_service_autocomplete(const CommandContext &ctx)
{
if (ctx.args.size() < 2)
shared_commands::std_autocomplete(ctx);
else
{
if (ctx.args.size() == 2)
{
std::set<std::string> templates = gTemplateManager().get_template_list();
for (const auto &template_name : templates)
std::cout << template_name << std::endl;
}
}
}
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent)
{
if (server_name.empty() || template_name.empty() || service_name.empty())
return false;
std::string service_dir = localpath::service(server_name, service_name);
if (service_dir.empty())
{
if (!silent)
{
std::cerr << "Error: Couldn't locate server " << server_name << " in any config directory" << std::endl;
std::cerr << "Please check the server name is correct and try again" << std::endl;
std::cerr << "You can list all servers with 'dropshell servers'" << std::endl;
std::cerr << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
}
return false;
}
if (std::filesystem::exists(service_dir))
{
if (!silent)
{
std::cerr << "Error: Service already exists: " << service_name << std::endl;
std::cerr << "Current service path: " << service_dir << std::endl;
}
return false;
}
template_info tinfo = gTemplateManager().get_template_info(template_name);
if (!tinfo.is_set())
{
if (!silent)
{
std::cerr << "Error: Template '" << template_name << "' not found" << std::endl;
std::cerr << "Please check the template name is correct and try again" << std::endl;
std::cerr << "You can list all templates with 'dropshell templates'" << std::endl;
std::cerr << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
}
return false;
}
// check template is all good.
if (!gTemplateManager().test_template(tinfo.local_template_path()))
{
if (!silent)
std::cerr << "Error: Template '" << template_name << "' is not valid" << std::endl;
return false;
}
// create the service directory
std::filesystem::create_directory(service_dir);
// copy the template config files to the service directory
recursive_copy(tinfo.local_template_path() / "config", service_dir);
if (!silent)
{
std::cout << "Service " << service_name << " created successfully" << std::endl;
std::cout << std::endl;
std::cout << "To complete the installation, please:" << std::endl;
std::cout << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl;
std::cout << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
}
return true;
}
} // namespace dropshell

View File

@ -23,7 +23,7 @@ struct EditCommandRegister {
CommandRegistry::instance().register_command({
edit_name_list,
edit_handler,
std_autocomplete,
shared_commands::std_autocomplete,
false, // hidden
false, // requires_config
false, // requires_install

View File

@ -22,7 +22,7 @@ namespace dropshell
{
CommandRegistry::instance().register_command({health_name_list,
health_handler,
std_autocomplete_allowall,
shared_commands::std_autocomplete_allowall,
false, // hidden
true, // requires_config
true, // requires_install
@ -36,72 +36,7 @@ namespace dropshell
}
} health_command_register;
// ------------------------------------------------------------------------------------------------
// health command implementation
HealthStatus is_healthy(const std::string &server, const std::string &service)
{
server_env_manager env(server);
if (!env.is_valid())
{
std::cerr << "Error: Server service not initialized" << std::endl;
return HealthStatus::ERROR;
}
if (!env.check_remote_dir_exists(remotepath::service(server, service)))
{
return HealthStatus::NOTINSTALLED;
}
std::string script_path = remotepath::service_template(server, service) + "/status.sh";
if (!env.check_remote_file_exists(script_path))
{
return HealthStatus::UNKNOWN;
}
// Run status script, does not display output.
if (!env.run_remote_template_command(service, "status", {}, true, {}))
return HealthStatus::UNHEALTHY;
return HealthStatus::HEALTHY;
}
std::string healthtick(const std::string &server, const std::string &service)
{
std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m";
std::string yellow_exclamation = "\033[33m!\033[0m";
std::string unknown = "\033[37m✓\033[0m";
HealthStatus status = is_healthy(server, service);
if (status == HealthStatus::HEALTHY)
return green_tick;
else if (status == HealthStatus::UNHEALTHY)
return red_cross;
else if (status == HealthStatus::UNKNOWN)
return unknown;
else
return yellow_exclamation;
}
std::string HealthStatus2String(HealthStatus status)
{
if (status == HealthStatus::HEALTHY)
return ":tick:";
else if (status == HealthStatus::UNHEALTHY)
return ":cross:";
else if (status == HealthStatus::UNKNOWN)
return ":greytick:";
else if (status == HealthStatus::NOTINSTALLED)
return ":warning:";
else
return ":error:";
}
std::string healthmark(const std::string &server, const std::string &service)
{
HealthStatus status = is_healthy(server, service);
return HealthStatus2String(status);
}
// ------------------------------------------------------------------------------------------------
// health command implementation
@ -121,7 +56,7 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server);
transwarp::parallel exec{services.size()};
auto task = transwarp::for_each(exec, services.begin(), services.end(), [&](const LocalServiceInfo& service) {
std::string status = healthtick(server, service.service_name);
std::string status = shared_commands::healthtick(server, service.service_name);
std::cout << status << " " << service.service_name << " (" << service.template_name << ")" << std::endl << std::flush;
});
task->wait();
@ -130,7 +65,7 @@ namespace dropshell
// get service status
std::string service = safearg(ctx.args, 1);
LocalServiceInfo service_info = get_service_info(server, service);
std::cout << healthtick(server, service) << " " << service << " (" << service_info.template_name << ")" << std::endl << std::flush;
std::cout << shared_commands::healthtick(server, service) << " " << service << " (" << service_info.template_name << ")" << std::endl << std::flush;
}
return 0;
}

View File

@ -6,6 +6,8 @@
#include "shared_commands.hpp"
#include "utils/hash.hpp"
#include "autogen/_agent.hpp"
#include "services.hpp"
#include <unistd.h>
#include <cstring>
#include <iostream>
@ -27,7 +29,7 @@ namespace dropshell
{
CommandRegistry::instance().register_command({install_name_list,
install_handler,
std_autocomplete_allowall,
shared_commands::std_autocomplete_allowall,
false, // hidden
false, // requires_config
false, // requires_install
@ -50,23 +52,6 @@ namespace dropshell
}
} install_command_register;
// ------------------------------------------------------------------------------------------------
// rsync_tree_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool rsync_tree_to_remote(
const std::string &local_path,
const std::string &remote_path,
server_env_manager &server_env,
bool silent)
{
ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync.");
std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " +
quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" +
remote_path + "/");
return execute_local_command(rsync_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
// ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND
@ -117,7 +102,7 @@ namespace dropshell
// Copy template files
std::cout << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_template(server, service) << "/" << std::endl;
if (!rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service),
if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service),
server_env, silent))
{
std::cerr << "Failed to copy template files using rsync" << std::endl;
@ -127,7 +112,7 @@ namespace dropshell
// Copy service files
std::cout << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_config(server, service) << std::endl;
if (!rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service),
if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service),
server_env, silent))
{
std::cerr << "Failed to copy service files using rsync" << std::endl;
@ -140,24 +125,10 @@ namespace dropshell
}
// print health tick
std::cout << "Health: " << healthtick(server, service) << std::endl;
std::cout << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true;
}
// ------------------------------------------------------------------------------------------------
// get_arch : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string get_arch()
{
// determine the architecture of the system
std::string arch;
#ifdef __aarch64__
arch = "arm64";
#elif __x86_64__
arch = "amd64";
#endif
return arch;
}
// ------------------------------------------------------------------------------------------------
// update_dropshell
@ -188,7 +159,7 @@ namespace dropshell
std::filesystem::path parent_path = dropshell_path.parent_path();
// determine the architecture of the system
std::string arch = get_arch();
std::string arch = shared_commands::get_arch();
std::string url = "https://gitea.jde.nz/public/dropshell/releases/download/latest/dropshell." + arch;
@ -327,7 +298,7 @@ namespace dropshell
// now create the agent.
// copy across from the local agent files.
rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false);
shared_commands::rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false);
// add in bb64. We can't use execute_remote_command() here, as that relies on bb64 which we're installing!
std::cout << "Installing bb64 on " << server << std::endl << std::flush;

View File

@ -6,6 +6,7 @@
#include "servers.hpp"
#include "tableprint.hpp"
#include "transwarp.hpp"
#include "server_env_manager.hpp"
#include <unistd.h>
#include <cstring>
@ -28,7 +29,7 @@ struct ListCommandRegister {
CommandRegistry::instance().register_command({
list_name_list,
list_handler,
std_autocomplete,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
@ -86,13 +87,13 @@ void list_servers() {
transwarp::parallel exec{servers.size()};
auto task = transwarp::for_each(exec, servers.begin(), servers.end(), [&](const ServerInfo& server) {
std::map<std::string, ServiceStatus> status = service_runner::get_all_services_status(server.name);
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server.name);
std::set<int> ports_used;
std::string serviceticks = "";
for (const auto& [service_name, service_status] : status) {
ports_used.insert(service_status.ports.begin(), service_status.ports.end());
serviceticks += HealthStatus2String(service_status.health) + " ";
serviceticks += shared_commands::HealthStatus2String(service_status.health) + " ";
}
std::string ports_used_str = "";
for (const auto& port : ports_used)
@ -163,12 +164,12 @@ void show_server_details(const std::string& server_name) {
tp.add_row({"Status", "Service", "Ports"});
std::map<std::string, ServiceStatus> status = service_runner::get_all_services_status(server_name);
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server_name);
std::set<int> ports_used;
std::string serviceticks = "";
for (const auto& [service_name, service_status] : status) {
std::string healthy = HealthStatus2String(service_status.health);
std::string healthy = shared_commands::HealthStatus2String(service_status.health);
std::string ports_str = "";
for (const auto& port : service_status.ports)

View File

@ -6,6 +6,7 @@
#include "utils/directories.hpp"
#include "servers.hpp"
#include "templates.hpp"
#include "utils/utils.hpp"
#include "utils/assert.hpp"
@ -22,7 +23,7 @@ struct NukeCommandRegister {
CommandRegistry::instance().register_command({
nuke_name_list,
nuke_handler,
std_autocomplete,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install

View File

@ -0,0 +1,247 @@
#include "shared_commands.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "server_env_manager.hpp"
#include "directories.hpp"
#include "services.hpp"
#include "servers.hpp"
namespace dropshell
{
namespace shared_commands
{
// ------------------------------------------------------------------------------------------------
// std_autocomplete : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
void std_autocomplete(const CommandContext &ctx)
{
if (ctx.args.size() == 0)
{ // just the command, no args yet.
// list servers
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto &server : servers)
{
std::cout << server.name << std::endl;
}
}
else if (ctx.args.size() == 1)
{
// list services
std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]);
for (const auto &service : services)
{
std::cout << service.service_name << std::endl;
}
}
}
// ------------------------------------------------------------------------------------------------
// std_autocomplete_allowall : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
void std_autocomplete_allowall(const CommandContext &ctx)
{
std_autocomplete(ctx);
if (ctx.args.size() == 1)
std::cout << "all" << std::endl;
}
// ------------------------------------------------------------------------------------------------
// rsync_tree_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool rsync_tree_to_remote(
const std::string &local_path,
const std::string &remote_path,
server_env_manager &server_env,
bool silent)
{
ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync.");
std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " +
quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" +
remote_path + "/");
return execute_local_command(rsync_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
// ------------------------------------------------------------------------------------------------
// get_arch : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string get_arch()
{
// determine the architecture of the system
std::string arch;
#ifdef __aarch64__
arch = "arm64";
#elif __x86_64__
arch = "amd64";
#endif
return arch;
}
// ------------------------------------------------------------------------------------------------
// cRemoteTempFolder : SHARED CLASS
// ------------------------------------------------------------------------------------------------
cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env)
{
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10);
std::string mkdir_cmd = "mkdir -p " + quote(p);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
std::cerr << "Failed to create temp directory on server" << std::endl;
else
mPath = p;
}
cRemoteTempFolder::~cRemoteTempFolder()
{
std::string rm_cmd = "rm -rf " + quote(mPath);
execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent);
}
std::string cRemoteTempFolder::path() const
{
return mPath;
}
// ------------------------------------------------------------------------------------------------
// get_all_services_status : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name)
{
std::map<std::string, ServiceStatus> status;
server_env_manager env(server_name);
if (!env.is_valid())
{
std::cerr << "Error: Invalid server environment" << std::endl;
return status;
}
std::string output;
if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}), cMode::CaptureOutput, &output))
return status;
std::stringstream ss(output);
std::string line;
while (std::getline(ss, line))
{
std::string key, value;
std::size_t pos = line.find("=");
if (pos != std::string::npos)
{
key = dequote(trim(line.substr(0, pos)));
value = dequote(trim(line.substr(pos + 1)));
// decode key, it's of format SERVICENAME_[HEALTH|PORTS]
std::string service_name = key.substr(0, key.find_last_of("_"));
std::string status_type = key.substr(key.find_last_of("_") + 1);
if (status_type == "HEALTH")
{ // healthy|unhealthy|unknown
if (value == "healthy")
status[service_name].health = HealthStatus::HEALTHY;
else if (value == "unhealthy")
status[service_name].health = HealthStatus::UNHEALTHY;
else if (value == "unknown")
status[service_name].health = HealthStatus::UNKNOWN;
else
status[service_name].health = HealthStatus::ERROR;
}
else if (status_type == "PORTS")
{ // port1,port2,port3
std::vector<std::string> ports = string2multi(value);
for (const auto &port : ports)
{
if (port != "unknown")
status[service_name].ports.push_back(str2int(port));
}
}
}
}
return status;
}
// ------------------------------------------------------------------------------------------------
// healthtick : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string healthtick(const std::string &server, const std::string &service)
{
std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m";
std::string yellow_exclamation = "\033[33m!\033[0m";
std::string unknown = "\033[37m✓\033[0m";
HealthStatus status = is_healthy(server, service);
if (status == HealthStatus::HEALTHY)
return green_tick;
else if (status == HealthStatus::UNHEALTHY)
return red_cross;
else if (status == HealthStatus::UNKNOWN)
return unknown;
else
return yellow_exclamation;
}
// ------------------------------------------------------------------------------------------------
// HealthStatus2String : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string HealthStatus2String(HealthStatus status)
{
if (status == HealthStatus::HEALTHY)
return ":tick:";
else if (status == HealthStatus::UNHEALTHY)
return ":cross:";
else if (status == HealthStatus::UNKNOWN)
return ":greytick:";
else if (status == HealthStatus::NOTINSTALLED)
return ":warning:";
else
return ":error:";
}
// ------------------------------------------------------------------------------------------------
// is_healthy : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
HealthStatus is_healthy(const std::string &server, const std::string &service)
{
server_env_manager env(server);
if (!env.is_valid())
{
std::cerr << "Error: Server service not initialized" << std::endl;
return HealthStatus::ERROR;
}
if (!env.check_remote_dir_exists(remotepath::service(server, service)))
{
return HealthStatus::NOTINSTALLED;
}
std::string script_path = remotepath::service_template(server, service) + "/status.sh";
if (!env.check_remote_file_exists(script_path))
{
return HealthStatus::UNKNOWN;
}
// Run status script, does not display output.
if (!env.run_remote_template_command(service, "status", {}, true, {}))
return HealthStatus::UNHEALTHY;
return HealthStatus::HEALTHY;
}
// ------------------------------------------------------------------------------------------------
// healthmark : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string healthmark(const std::string &server, const std::string &service)
{
HealthStatus status = is_healthy(server, service);
return HealthStatus2String(status);
}
} // namespace shared_commands
} // namespace dropshell

View File

@ -3,27 +3,61 @@
#include "servers.hpp"
#include "command_registry.hpp"
#include "server_env_manager.hpp"
namespace dropshell {
namespace dropshell
{
namespace shared_commands
{
typedef enum HealthStatus
{
HEALTHY,
UNHEALTHY,
NOTINSTALLED,
ERROR,
UNKNOWN
} HealthStatus;
typedef struct ServiceStatus
{
HealthStatus health;
std::vector<int> ports;
} ServiceStatus;
// expose routines used by multiple commands.
class cRemoteTempFolder
{
public:
cRemoteTempFolder(const server_env_manager &server_env); // create a temp folder on the remote server
~cRemoteTempFolder(); // delete the temp folder on the remote server
std::string path() const; // get the path to the temp folder on the remote server
private:
std::string mPath;
const server_env_manager &mServerEnv;
};
// defined in install.cpp
bool rsync_tree_to_remote(
const std::string &local_path,
const std::string &remote_path,
server_env_manager &server_env,
const std::string &remote_path,
server_env_manager &server_env,
bool silent);
// defined in install.cpp
std::string get_arch();
// defined in health.cpp
std::string healthtick(const std::string& server, const std::string& service);
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
std::string healthtick(const std::string &server, const std::string &service);
std::string HealthStatus2String(HealthStatus status);
HealthStatus is_healthy(const std::string &server, const std::string &service);
std::string healthmark(const std::string &server, const std::string &service);
// defined in standard_autocomplete.cpp
void std_autocomplete(const CommandContext& ctx);
void std_autocomplete_allowall(const CommandContext& ctx);
void std_autocomplete(const CommandContext &ctx);
void std_autocomplete_allowall(const CommandContext &ctx);
} // namespace shared_commands
} // namespace dropshell
#endif

View File

@ -1,36 +0,0 @@
#include "shared_commands.hpp"
#include "command_registry.hpp"
#include "servers.hpp"
#include "services.hpp"
#include "utils/assert.hpp"
namespace dropshell {
void std_autocomplete(const CommandContext &ctx)
{
if (ctx.args.size() == 0) { // just the command, no args yet.
// list servers
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto& server : servers) {
std::cout << server.name << std::endl;
}
}
else if (ctx.args.size() == 1) {
// list services
std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]);
for (const auto& service : services) {
std::cout << service.service_name << std::endl;
}
}
}
void std_autocomplete_allowall(const CommandContext &ctx)
{
std_autocomplete(ctx);
if (ctx.args.size() == 1)
std::cout << "all" << std::endl;
}
} // namespace dropshell

View File

@ -4,6 +4,8 @@
#include "templates.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "services.hpp"
namespace dropshell
{
@ -19,7 +21,7 @@ namespace dropshell
{
CommandRegistry::instance().register_command({uninstall_name_list,
uninstall_handler,
std_autocomplete_allowall,
shared_commands::std_autocomplete_allowall,
false, // hidden
true, // requires_config
true, // requires_install
@ -39,7 +41,7 @@ namespace dropshell
bool uninstall_service(const std::string &server, const std::string &service, bool silent)
bool uninstall_service(const std::string &server, const std::string &service, bool silent=false)
{
if (!silent)
maketitle("Uninstalling " + service + " on " + server);
@ -97,14 +99,14 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
{
if (!uninstall_service(server, service.service_name, false))
if (!uninstall_service(server, service.service_name))
okay = false;
}
return okay ? 0 : 1;
}
std::string service = safearg(ctx.args, 1);
return uninstall_service(server, service, false) ? 0 : 1;
return uninstall_service(server, service) ? 0 : 1;
}