375 lines
15 KiB
C++
375 lines
15 KiB
C++
#include "command_registry.hpp"
|
|
#include "config.hpp"
|
|
#include "utils/utils.hpp"
|
|
#include "utils/directories.hpp"
|
|
#include "templates.hpp"
|
|
#include "shared_commands.hpp"
|
|
#include "utils/hash.hpp"
|
|
#include "utils/createagent.hpp"
|
|
#include <unistd.h>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <filesystem>
|
|
#include "utils/assert.hpp"
|
|
|
|
namespace dropshell
|
|
{
|
|
|
|
int install_handler(const CommandContext &ctx);
|
|
|
|
static std::vector<std::string> install_name_list = {"install", "reinstall", "update"};
|
|
|
|
// Static registration
|
|
struct InstallCommandRegister
|
|
{
|
|
InstallCommandRegister()
|
|
{
|
|
CommandRegistry::instance().register_command({install_name_list,
|
|
install_handler,
|
|
std_autocomplete_allowallservices,
|
|
false, // hidden
|
|
false, // requires_config
|
|
false, // requires_install
|
|
0, // min_args (after command)
|
|
2, // max_args (after command)
|
|
"install SERVER [SERVICE]",
|
|
"Install/reinstall service(s). Safe/non-destructive way to update.",
|
|
// heredoc
|
|
R"(
|
|
Install components on a server. This is safe to re-run (non-destructive) and used to update
|
|
servers and their services.
|
|
|
|
install (re)install dropshell components on this computer.
|
|
install SERVER (re)install dropshell agent on the given server.
|
|
install SERVER [SERVICE|*] (re)install the given service (or all services) on the given server.
|
|
|
|
Note you need to create the service first with:
|
|
dropshell create-service <server> <template> <service>
|
|
)"});
|
|
}
|
|
} 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
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool install_service(const std::string &server, const std::string &service, bool silent)
|
|
{
|
|
LocalServiceInfo service_info = get_service_info(server, service);
|
|
if (!SIvalid(service_info))
|
|
return false;
|
|
|
|
server_env_manager server_env(server);
|
|
if (!server_env.is_valid())
|
|
return false;
|
|
|
|
maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
|
|
|
|
if (!server_env.is_valid())
|
|
return false; // should never hit this.
|
|
|
|
// Check if template exists
|
|
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
|
|
if (!tinfo.is_set())
|
|
return false;
|
|
|
|
// Create service directory
|
|
std::string remote_service_path = remotepath::service(server, service);
|
|
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
|
|
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
|
|
{
|
|
std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Check if rsync is installed on remote host
|
|
std::string check_rsync_cmd = "which rsync";
|
|
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent))
|
|
{
|
|
std::cerr << "rsync is not installed on the remote host" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// 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),
|
|
server_env, silent))
|
|
{
|
|
std::cerr << "Failed to copy template files using rsync" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// 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),
|
|
server_env, silent))
|
|
{
|
|
std::cerr << "Failed to copy service files using rsync" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Run install script
|
|
{
|
|
server_env.run_remote_template_command(service, "install", {}, silent, {});
|
|
}
|
|
|
|
// print health tick
|
|
std::cout << "Health: " << 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
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
std::string _exec(const char *cmd)
|
|
{
|
|
char buffer[128];
|
|
std::string result = "";
|
|
FILE *pipe = popen(cmd, "r");
|
|
if (!pipe)
|
|
{
|
|
throw std::runtime_error("popen() failed!");
|
|
}
|
|
while (!feof(pipe))
|
|
{
|
|
if (fgets(buffer, 128, pipe) != nullptr)
|
|
result += buffer;
|
|
}
|
|
pclose(pipe);
|
|
return trim(result);
|
|
}
|
|
|
|
int update_dropshell()
|
|
{
|
|
// determine path to this executable
|
|
std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe");
|
|
std::filesystem::path parent_path = dropshell_path.parent_path();
|
|
|
|
// determine the architecture of the system
|
|
std::string arch = get_arch();
|
|
|
|
std::string url = "https://gitea.jde.nz/public/dropshell/releases/download/latest/dropshell." + arch;
|
|
|
|
// download new version, preserve permissions and ownership
|
|
std::string bash_script;
|
|
bash_script += "docker run --rm -v " + parent_path.string() + ":/target";
|
|
bash_script += " gitea.jde.nz/public/debian-curl:latest";
|
|
bash_script += " sh -c \"";
|
|
bash_script += " curl -fsSL " + url + " -o /target/dropshell_temp &&";
|
|
bash_script += " chmod --reference=/target/dropshell /target/dropshell_temp &&";
|
|
bash_script += " chown --reference=/target/dropshell /target/dropshell_temp";
|
|
bash_script += "\"";
|
|
|
|
std::string cmd = "bash -c '" + bash_script + "'";
|
|
int rval = system(cmd.c_str());
|
|
if (rval != 0)
|
|
{
|
|
std::cerr << "Failed to download new version of dropshell." << std::endl;
|
|
return -1;
|
|
}
|
|
|
|
// check if the new version is the same as the old version
|
|
uint64_t new_hash = hash_file(parent_path / "dropshell_temp");
|
|
uint64_t old_hash = hash_file(parent_path / "dropshell");
|
|
if (new_hash == old_hash)
|
|
{
|
|
std::cout << "Confirmed dropshell is the latest version." << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
std::string runvercmd = (parent_path / "dropshell").string() + " version";
|
|
std::string currentver = _exec(runvercmd.c_str());
|
|
runvercmd = (parent_path / "dropshell_temp").string() + " version";
|
|
std::string newver = _exec(runvercmd.c_str());
|
|
|
|
if (currentver >= newver)
|
|
{
|
|
std::cout << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl;
|
|
std::cout << "No update needed." << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
|
|
std::string bash_script_2 = "docker run --rm -v " + parent_path.string() + ":/target gitea.jde.nz/public/debian-curl:latest " +
|
|
"sh -c \"mv /target/dropshell_temp /target/dropshell\"";
|
|
rval = system(bash_script_2.c_str());
|
|
if (rval != 0)
|
|
{
|
|
std::cerr << "Failed to install new version of dropshell." << std::endl;
|
|
return -1;
|
|
}
|
|
|
|
std::cout << "Successfully updated " << dropshell_path << " to the latest " << arch << " version." << std::endl;
|
|
|
|
// execute the new version
|
|
execlp("bash", "bash", "-c", (parent_path / "dropshell").c_str(), "install", (char *)nullptr);
|
|
std::cerr << "Failed to execute new version of dropshell." << std::endl;
|
|
return -1;
|
|
}
|
|
|
|
int install_local_agent()
|
|
{
|
|
std::vector<std::filesystem::path> paths = {
|
|
gConfig().get_local_template_cache_path(),
|
|
gConfig().get_local_backup_path(),
|
|
gConfig().get_local_tempfiles_path(),
|
|
localpath::agent()};
|
|
for (auto &p : gConfig().get_local_server_definition_paths())
|
|
paths.push_back(p);
|
|
|
|
for (auto &p : paths)
|
|
if (!std::filesystem::exists(p))
|
|
{
|
|
std::cout << "Creating directory: " << p << std::endl;
|
|
std::filesystem::create_directories(p);
|
|
}
|
|
|
|
// download bb64.
|
|
if (!std::filesystem::exists(localpath::agent() + "bb64"))
|
|
{
|
|
std::string cmd = "cd " + localpath::agent() + " && curl -fsSL -o bb64 https://gitea.jde.nz/public/bb64/releases/download/latest/bb64.amd64 && chmod a+x bb64";
|
|
int ret = system(cmd.c_str());
|
|
if (EXITSTATUSCHECK(ret))
|
|
std::cout << "Downloaded bb64 to " << localpath::agent() << std::endl;
|
|
else
|
|
std::cerr << "Failed to download bb64 to " << localpath::agent() << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Updating bb64..." << std::endl;
|
|
system((localpath::agent() + "bb64 -u").c_str()); // update.
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int install_host()
|
|
{
|
|
// update dropshell.
|
|
// install the local dropshell agent.
|
|
|
|
int rval = update_dropshell();
|
|
if (rval != 0)
|
|
return rval;
|
|
|
|
rval = install_local_agent();
|
|
if (rval != 0)
|
|
return rval;
|
|
|
|
std::cout << "Installation complete." << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
int install_server(const std::string &server)
|
|
{
|
|
// install the dropshell agent on the given server.
|
|
std::cout << "Installing dropshell agent on " << server << std::endl;
|
|
|
|
std::string agent_path = remotepath::agent(server);
|
|
if (agent_path.empty())
|
|
{
|
|
std::cerr << "Failed to get agent path for " << server << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
server_env_manager server_env(server);
|
|
if (!server_env.is_valid())
|
|
{
|
|
std::cerr << "Invalid server environment for " << server << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// first install bb64.
|
|
std::cout << "Installing bb64 on " << server << std::endl
|
|
<< std::flush;
|
|
std::string remote_cmd =
|
|
"ssh -p " + server_env.get_SSH_INFO().port + " " + server_env.get_SSH_INFO().user + "@" + server_env.get_SSH_INFO().host +
|
|
" 'mkdir -p " + quote(agent_path) + " && curl -fsSL \"https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh\" | bash -s -- " +
|
|
quote(agent_path) + " " + quote("$(id -u " + server_env.get_SSH_USER() + "):$(id -g " + server_env.get_SSH_USER() + ")") + "'";
|
|
|
|
std::cout << "Executing: " << remote_cmd << std::endl;
|
|
if (!execute_local_command(remote_cmd, nullptr, cMode::Silent))
|
|
std::cerr << "Failed to download bb64 to " << agent_path << " on remote server." << std::endl;
|
|
else
|
|
std::cout << "Downloaded bb64 to " << agent_path << " on remote server." << std::endl;
|
|
|
|
// now create the agent.
|
|
create_agent(server);
|
|
|
|
return 0; // NOTIMPL
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// install command implementation
|
|
// ------------------------------------------------------------------------------------------------
|
|
int install_handler(const CommandContext &ctx)
|
|
{
|
|
if (ctx.args.size() < 1)
|
|
{ // install host
|
|
return install_host();
|
|
}
|
|
|
|
std::string server = safearg(ctx.args, 0);
|
|
|
|
if (ctx.args.size() == 1)
|
|
{ // install server
|
|
return install_server(server);
|
|
}
|
|
|
|
// install service(s)
|
|
if (safearg(ctx.args, 1) == "*")
|
|
{
|
|
// install all services on the server
|
|
bool okay = true;
|
|
std::vector<LocalServiceInfo> services = get_server_services_info(server);
|
|
for (const auto &service : services)
|
|
{
|
|
if (!install_service(server, service.service_name, false))
|
|
okay = false;
|
|
}
|
|
return okay ? 0 : 1;
|
|
}
|
|
else
|
|
{ // install the specific service.
|
|
std::string service = safearg(ctx.args, 1);
|
|
return install_service(server, service, false) ? 0 : 1;
|
|
}
|
|
}
|
|
|
|
} // namespace dropshell
|