Files
dropshell/source/src/services.cpp
2025-12-30 08:43:06 +13:00

266 lines
10 KiB
C++

#include "services.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp"
#include "templates.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include <libassert/assert.hpp>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
namespace dropshell
{
bool SIvalid(const LocalServiceInfo &service_info)
{
return !service_info.service_name.empty() &&
!service_info.template_name.empty() &&
!service_info.local_service_path.empty() &&
!service_info.local_template_path.empty() &&
!service_info.user.empty();
}
std::vector<LocalServiceInfo> get_server_services_info(const std::string &server_name, bool skip_update)
{
std::vector<LocalServiceInfo> services;
if (server_name.empty())
return services;
std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
if (local_server_definition_paths.empty())
{
error << "No local server definition paths found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl;
return services;
}
for (const auto &server_definition_path : local_server_definition_paths)
{
fs::path serverpath = server_definition_path + "/" + server_name;
if (fs::exists(serverpath)) // service is on that server...
for (const auto &entry : fs::directory_iterator(serverpath))
{
if (fs::is_directory(entry))
{
std::string dirname = entry.path().filename().string();
if (dirname.empty() || dirname[0] == '.' || dirname[0] == '_')
continue;
auto service = get_service_info(server_name, dirname, skip_update);
if (!service.local_service_path.empty())
services.push_back(service);
else
warning << "Failed to get service info for " << dirname << " on server " << server_name << std::endl;
}
} // end of for
}
return services;
}
bool get_bool_variable(const ordered_env_vars &variables, const std::string &variable_name)
{
auto it = find_var(variables, variable_name);
if (it == variables.end())
{
error << "Variable " << variable_name << " not found in the service " << filenames::template_info_env << std::endl;
return false;
}
return it->second == "true";
}
LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name, bool skip_update)
{
LocalServiceInfo service;
if (server_name.empty() || service_name.empty())
return LocalServiceInfo();
if (!legal_service_name(service_name))
return LocalServiceInfo();
service.service_name = service_name;
service.local_service_path = localpath::service(server_name, service_name);
if (service.local_service_path.empty())
return LocalServiceInfo();
// check the service directory exists.
if (!fs::exists(service.local_service_path))
{
warning << "Service directory not found: " << service.local_service_path << std::endl;
return LocalServiceInfo();
}
// now set the template name and path.
ordered_env_vars variables;
if (!get_all_service_env_vars(server_name, service_name, variables))
return LocalServiceInfo();
{ // confirm TEMPLATE is defined.
auto it = find_var(variables, "TEMPLATE");
if (it == variables.end())
{
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
return LocalServiceInfo();
}
service.template_name = it->second;
}
template_info tinfo = gTemplateManager().get_template_info(service.template_name,skip_update);
if (!tinfo.is_set())
{
// Template not found - this means it's not available locally OR from registry
error << "Template '" << service.template_name << "' not found locally or in registry" << std::endl;
return LocalServiceInfo();
}
// Template is available (either locally or downloaded from registry)
service.local_template_path = tinfo.local_template_path();
{ // set the user.
auto it = find_var(variables, "SSH_USER");
if (it == variables.end())
{
error << "SSH_USER variable not defined in service " << service_name << " on server " << server_name << std::endl;
return LocalServiceInfo();
}
service.user = it->second;
}
// set the host root and docker requirements.
service.requires_host_root = get_bool_variable(variables, "REQUIRES_HOST_ROOT");
service.requires_docker = get_bool_variable(variables, "REQUIRES_DOCKER");
service.requires_docker_root = get_bool_variable(variables, "REQUIRES_DOCKER_ROOT");
return service;
}
std::set<std::string> get_used_commands(const std::string &server_name, const std::string &service_name)
{
std::set<std::string> commands;
if (server_name.empty() || service_name.empty())
return commands;
auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty())
{
error << "Service not found: " << service_name << std::endl;
return commands;
}
// iterate over all files in the template path, and add the command name to the set.
// commands are .sh files that don't begin with _
for (const auto &entry : fs::directory_iterator(service_info.local_template_path))
{
if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0))
commands.insert(entry.path().stem().string());
}
return commands;
}
std::set<std::string> list_backups(const std::string &server_name, const std::string &service_name)
{
std::set<std::string> backups;
if (server_name.empty() || service_name.empty())
return backups;
// need to find the template for the service.
auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty())
{
error << "Service not found: " << service_name << std::endl;
return backups;
}
std::string backups_dir = localpath::backups();
if (backups_dir.empty())
return backups;
if (fs::exists(backups_dir))
{
for (const auto &entry : fs::directory_iterator(backups_dir))
{
if (fs::is_regular_file(entry) && entry.path().extension() == ".tgz")
if (entry.path().filename().string().find(service_info.template_name) != std::string::npos)
{
backups.insert(entry.path().filename().string());
}
}
}
return backups;
}
bool get_all_service_env_vars(const std::string &server_name, const std::string &service_name, ordered_env_vars &all_env_vars)
{
clear_vars(all_env_vars);
if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name)))
{
error << "Service not found: " << service_name << " on server " << server_name << std::endl;
return false;
}
// Lambda function to load environment variables from a file
auto load_env_file = [&all_env_vars](const std::string &file)
{
if (!file.empty() && std::filesystem::exists(file))
{
ordered_env_vars env_vars;
envmanager env_manager(file);
env_manager.load();
env_manager.get_all_variables(env_vars);
merge_vars(all_env_vars, env_vars);
}
else
warning << "Expected environment file not found: " << file << std::endl;
};
// add in some simple variables first, as others below may depend on/use these in bash.
set_var(all_env_vars, "SERVER", server_name);
set_var(all_env_vars, "SERVICE", service_name);
set_var(all_env_vars, "DOCKER_CLI_HINTS", "false"); // turn off docker junk.
// Load environment files
load_env_file(localfile::service_env(server_name, service_name));
std::string template_name = get_var(all_env_vars, "TEMPLATE");
if (template_name.empty())
{
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
return false;
}
auto tinfo = gTemplateManager().get_template_info(template_name, true); // skip updates.
if (!tinfo.is_set())
{
// Template is not available locally or from registry
error << "Template '" << template_name << "' not found locally or in registry" << std::endl;
return false;
}
ASSERT(std::filesystem::exists(tinfo.local_template_info_env_path()));
load_env_file(tinfo.local_template_info_env_path());
std::string user = get_var(all_env_vars, "SSH_USER");
if (user.empty())
{
error << "SSH_USER variable not defined in service " << service_name << " on server " << server_name << std::endl;
info << "This variable definition is always required, and usually set in the "<<filenames::service_env << " file." << std::endl;
info << "Please check " << localfile::service_env(server_name, service_name) << std::endl;
return false;
}
// more additional, these depend on others above.
set_var(all_env_vars, "CONFIG_PATH", remotepath(server_name, user).service_config(service_name));
set_var(all_env_vars, "AGENT_PATH", remotepath(server_name, user).agent());
return true;
}
} // namespace dropshell