dropshell/src/servers.cpp
2025-05-04 20:04:47 +12:00

222 lines
8.3 KiB
C++

#include "servers.hpp"
#include "server_env_manager.hpp"
#include "service_runner.hpp"
#include "utils/tableprint.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp"
#include "services.hpp"
#include "config.hpp"
#include "templates.hpp"
#include "contrib/transwarp.hpp"
#include <iostream>
#include <fstream>
#include <iomanip>
#include <filesystem>
namespace dropshell {
std::vector<ServerInfo> get_configured_servers() {
std::vector<ServerInfo> servers;
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty())
return servers;
for (auto servers_dir : lsdp) {
if (!servers_dir.empty() && std::filesystem::exists(servers_dir)) {
for (const auto& entry : std::filesystem::directory_iterator(servers_dir)) {
if (std::filesystem::is_directory(entry)) {
std::string server_name = entry.path().filename().string();
if (server_name.empty() || server_name[0]=='.' || server_name[0]=='_')
continue;
server_env_manager env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl;
continue;
}
servers.push_back({
server_name,
env.get_SSH_HOST(),
env.get_SSH_USER(),
env.get_SSH_PORT()
});
}
}
}
}
return servers;
}
ServerInfo get_server_info(const std::string &server_name)
{
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty())
return ServerInfo();
for (auto &config_dir : lsdp) {
std::string server_dir = config_dir + "/" + server_name;
if (std::filesystem::exists(server_dir)) {
server_env_manager env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << server_dir << std::endl;
continue;
}
return ServerInfo({server_name, env.get_SSH_HOST(), env.get_SSH_USER(), env.get_SSH_PORT()});
}
}
return ServerInfo();
}
// https://github.com/bloomen/transwarp?tab=readme-ov-file#range-functions
void list_servers() {
auto servers = get_configured_servers();
tableprint tp("All DropShell Servers");
tp.add_row({"Name", "User", "Address", "Health", "Ports"});
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::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 += service_runner::HealthStatus2String(service_status.health) + " ";
}
std::string ports_used_str = "";
for (const auto& port : ports_used)
ports_used_str += std::to_string(port) + " ";
tp.add_row({server.name, server.ssh_user, server.ssh_host, serviceticks, ports_used_str});
});
task->wait();
tp.print();
}
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<std::string, ServiceStatus> status = service_runner::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 = 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
void create_server(const std::string &server_name)
{
// 1. check if server name already exists
std::string server_existing_dir = localpath::server(server_name);
if (!server_existing_dir.empty()) {
std::cerr << "Error: Server name already exists: " << server_name << std::endl;
std::cerr << "Current server path: " << server_existing_dir << std::endl;
return;
}
// 2. create a new directory in the user config directory
auto lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty() || lsdp[0].empty()) {
std::cerr << "Error: Local server definition path not found - is DropShell initialised?" << std::endl;
return;
}
std::string server_dir = lsdp[0] + "/" + server_name;
std::filesystem::create_directory(server_dir);
// 3. create a template server.env file in the server directory
std::string user = getenv("USER");
std::string server_env_path = server_dir + "/server.env";
std::ofstream server_env_file(server_env_path);
server_env_file << "SSH_HOST=" << server_name << std::endl;
server_env_file << "SSH_USER=" << user << std::endl;
server_env_file << "SSH_PORT=" << 22 << std::endl;
server_env_file << std::endl;
server_env_file << "DROPSHELL_DIR=/home/"+user+"/.dropshell" << std::endl;
server_env_file.close();
// 4. add dropshell-agent service to server
create_service(server_name, "dropshell-agent", "dropshell-agent", true); // silently create service.
std::cout << "Server created successfully: " << server_name << std::endl;
std::cout << "Please complete the installation:" <<std::endl;
std::cout << "1) edit the server configuration: dropshell edit " << server_name << std::endl;
std::cout << "2) test ssh is working: dropshell ssh " << server_name << std::endl;
std::cout << "3) install dropshell-agent: dropshell install " << server_name << " dropshell-agent" << std::endl;
std::cout << std::endl;
}
void get_all_used_commands(std::set<std::string> &commands)
{
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto& server : servers)
{
auto services = dropshell::get_server_services_info(server.name);
for (const auto& service : services)
commands.merge(dropshell::get_used_commands(server.name, service.service_name));
}
}
} // namespace dropshell