dropshell/source/src/commands/shared_commands.cpp
Your Name 60907e5e02
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 11s
List tidy
2025-05-24 17:00:43 +12:00

341 lines
16 KiB
C++

#include "shared_commands.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "servers.hpp"
#include "directories.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "utils/output.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<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers)
{
rawout << server.get_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)
{
rawout << 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)
rawout << "all" << std::endl;
}
// ------------------------------------------------------------------------------------------------
// rsync_tree_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool rsync_tree_to_remote(
const std::string &local_path,
const std::string &remote_path,
const ServerConfig &server_env,
bool silent,
std::string user)
{
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(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 ServerConfig &server_env, std::string user) :
mServerEnv(server_env), mUser(user)
{
std::string p = remotepath(server_env.get_server_name(),user).temp_files() + "/" + random_alphanumeric_string(10);
std::string mkdir_cmd = "mkdir -p " + quote(p);
if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", mkdir_cmd, {}), cMode::Silent))
error << "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(mUser), 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(const ServerConfig & server_env)
{
std::map<std::string, ServiceStatus> status;
for (const auto& user : server_env.get_users()) {
status.merge(get_all_services_status(server_env, user.user));
}
return status;
}
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env, std::string user)
{
std::map<std::string, ServiceStatus> status;
std::string server_name = server_env.get_server_name();
std::string output;
std::string agentpath = remotepath(server_name,user).agent();
if (!execute_ssh_command(server_env.get_SSH_INFO(user),
sCommand(agentpath, "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", agentpath}}),
cMode::Silent,
&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)
{
ServerConfig env(server);
if (!env.is_valid())
{
error << "Server service not initialized" << std::endl;
return HealthStatus::ERROR;
}
std::string user = env.get_user_for_service(service);
if (!env.check_remote_dir_exists(remotepath(server,user).service(service), user))
{
return HealthStatus::NOTINSTALLED;
}
std::string script_path = remotepath(server,user).service_template(service) + "/status.sh";
if (!env.check_remote_file_exists(script_path, user))
{
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);
}
// ------------------------------------------------------------------------------------------------
// cBackupFileName : SHARED CLASS
// ------------------------------------------------------------------------------------------------
cBackupFileName::cBackupFileName(const std::string &server, const std::string &service, const std::string &template_name)
{
mServer = server;
mService = service;
mTemplateName = template_name;
// 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");
mDatetime = datetime.str();
}
cBackupFileName::cBackupFileName(const std::string &filename)
{
// Parse the filename according to the format:
// server + magic_string() + template_name + magic_string() + service + magic_string() + datetime + ".tgz"
std::string name = filename;
if (name.size() > 4 && name.substr(name.size() - 4) == ".tgz")
name = name.substr(0, name.size() - 4);
std::string sep = magic_string();
size_t first = name.find(sep);
size_t second = name.find(sep, first + sep.size());
size_t third = name.find(sep, second + sep.size());
if (first == std::string::npos || second == std::string::npos || third == std::string::npos)
{
mServer = mService = mTemplateName = mDatetime = "";
return;
}
mServer = name.substr(0, first);
mTemplateName = name.substr(first + sep.size(), second - (first + sep.size()));
mService = name.substr(second + sep.size(), third - (second + sep.size()));
mDatetime = name.substr(third + sep.size());
}
std::string cBackupFileName::get_filename() const
{
return mServer + magic_string() + mTemplateName + magic_string() + mService + magic_string() + mDatetime + ".tgz";
}
std::string cBackupFileName::get_server() const { return mServer; }
std::string cBackupFileName::get_service() const { return mService; }
std::string cBackupFileName::get_template_name() const { return mTemplateName; }
std::string cBackupFileName::get_datetime() const { return mDatetime; }
bool cBackupFileName::is_valid() const
{
// All fields must be non-empty, and none may contain the magic string
return !mServer.empty() && !mService.empty() && !mTemplateName.empty() && !mDatetime.empty() &&
!has_magic_string(mServer) && !has_magic_string(mService) && !has_magic_string(mTemplateName);
}
// ------------------------------------------------------------------------------------------------
// scp_file_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user)
{
if (!server_env.is_valid())
{
error << "Invalid server environment" << std::endl;
return false;
}
ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp.");
std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + quote(local_path) + " " + user + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : "");
return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
// ------------------------------------------------------------------------------------------------
// scp_file_from_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user)
{
if (!server_env.is_valid())
{
error << "Invalid server environment" << std::endl;
return false;
}
ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp.");
std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + user + "@" + server_env.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));
}
} // namespace shared_commands
} // namespace dropshell