Compare commits

...

13 Commits

Author SHA1 Message Date
60907e5e02 List tidy
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 11s
2025-05-24 17:00:43 +12:00
b3398582ca dropshell release 2025.0524.1314
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 12s
2025-05-24 13:14:51 +12:00
763293c7d0 dropshell release 2025.0524.1149
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 17s
2025-05-24 11:49:24 +12:00
0934179053 ...
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-24 11:15:23 +12:00
e1c631dc72 Fix list a bit
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-23 23:07:26 +12:00
b07704612b .
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-23 22:24:28 +12:00
462d215d5c Big refactor
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-23 22:06:37 +12:00
048345c636 Broken 2025-05-23 21:41:33 +12:00
94f77994f0 .
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 21:38:40 +12:00
b3a57f13dc dropshell release 2025.0521.2125
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 21:25:39 +12:00
270d6ef792 Tidying
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 20:51:05 +12:00
9063edb45f create-server
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 19:30:10 +12:00
fc6b310b89 dropshell release 2025.0521.1908
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 19:08:48 +12:00
41 changed files with 2124 additions and 1494 deletions

View File

@ -1,5 +1,5 @@
SSH_ADDRESS=localhost SSH_ADDRESS=localhost
SSH_PORT=22 SSH_PORT=22
SSH_USER=$(whoami) SSH_UNPRIVILEGED_USER=$(whoami)

View File

@ -50,7 +50,7 @@ function install_bb64() {
_die "Curl is not installed. Curl is required for agent installation." _die "Curl is not installed. Curl is required for agent installation."
fi fi
curl -fsSL "https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh" | bash -s -- "$AGENT_PATH" "$(id -u $USER):$(id -g $USER)" curl -fsSL "https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh" | bash -s -- "$AGENT_LOCAL_PATH" "$(id -u $USER):$(id -g $USER)"
# test result code from curl # test result code from curl
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -58,7 +58,7 @@ function install_bb64() {
fi fi
# test if bb64 is installed # test if bb64 is installed
"$AGENT_PATH/bb64" -v "$AGENT_LOCAL_PATH/bb64" -v
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
_die "bb64 did not install correctly." _die "bb64 did not install correctly."
fi fi
@ -71,11 +71,11 @@ function install_bb64() {
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
set -a set -a
AGENT_PATH="$SCRIPT_DIR" AGENT_LOCAL_PATH="$SCRIPT_DIR"
set +a set +a
_check_required_env_vars "AGENT_PATH" _check_required_env_vars "AGENT_LOCAL_PATH"
echo "Installing host agent into $AGENT_PATH" echo "Installing host agent into $AGENT_LOCAL_PATH"
_check_docker_installed || _die "Docker is required." _check_docker_installed || _die "Docker is required."

View File

@ -0,0 +1 @@
TODO!

View File

@ -23,7 +23,6 @@ SCRIPT_DIR="$(dirname "$0")"
# // |-- service.env (actual service config) # // |-- service.env (actual service config)
# // |-- .template_info.env # // |-- .template_info.env
# // |-- template # // |-- template
# // |-- _default.env
# // |-- (script files) # // |-- (script files)
# // |-- config # // |-- config
# // |-- service.env (default service config) # // |-- service.env (default service config)
@ -66,7 +65,6 @@ function run_command() {
CURRENT_OUTPUT=$( CURRENT_OUTPUT=$(
set -a set -a
load_dotenv "${service_path}/template/_default.env"
load_dotenv "${service_path}/config/service.env" load_dotenv "${service_path}/config/service.env"
load_dotenv "${service_path}/config/.template_info.env" load_dotenv "${service_path}/config/.template_info.env"

View File

@ -59,7 +59,7 @@ bool autocomplete(const std::vector<std::string> &args)
{ {
auto servers = dropshell::get_configured_servers(); auto servers = dropshell::get_configured_servers();
for (const auto& server : servers) for (const auto& server : servers)
std::cout << server.name << std::endl; std::cout << server.get_server_name() << std::endl;
return true; return true;
} }

View File

@ -11,7 +11,7 @@
#include "config.hpp" #include "config.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
@ -53,14 +53,10 @@ namespace dropshell
namespace shared_commands namespace shared_commands
{ {
bool backupdata_service(const std::string &server, const std::string &service) bool backupdata_service(const ServerConfig &server_env, const std::string &service)
{ {
server_env_manager server_env(server); ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name());
if (!server_env.is_valid()) std::string server = server_env.get_server_name();
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
LocalServiceInfo sinfo = get_service_info(server, service); LocalServiceInfo sinfo = get_service_info(server, service);
if (!SIvalid(sinfo)) if (!SIvalid(sinfo))
@ -78,13 +74,15 @@ namespace dropshell
return true; // nothing to back up. return true; // nothing to back up.
} }
std::string user = server_env.get_user_for_service(service);
// Check if basic installed stuff is in place. // Check if basic installed stuff is in place.
std::string remote_service_template_path = remotepath::service_template(server, service); std::string remote_service_template_path = remotepath(server, user).service_template(service);
std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh"; std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
std::string remote_service_config_path = remotepath::service_config(server, service); std::string remote_service_config_path = remotepath(server, user).service_config(service);
if (!server_env.check_remote_items_exist({remotepath::service(server, service), if (!server_env.check_remote_items_exist({remotepath(server, user).service(service),
remote_command_script_file, remote_command_script_file,
remotefile::service_env(server, service)})) remotefile(server, user).service_env(service)}, user))
{ {
error << "Error: Required service directories not found on remote server" << std::endl; error << "Error: Required service directories not found on remote server" << std::endl;
info << "Is the service installed?" << std::endl; info << "Is the service installed?" << std::endl;
@ -92,17 +90,17 @@ namespace dropshell
} }
// Create backups directory on server if it doesn't exist // Create backups directory on server if it doesn't exist
std::string remote_backups_dir = remotepath::backups(server); std::string remote_backups_dir = remotepath(server, user).backups();
debug << "Remote backups directory on " << server << ": " << remote_backups_dir << std::endl; debug << "Remote backups directory on " << server << ": " << remote_backups_dir << std::endl;
std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir); std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Defaults)) if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", mkdir_cmd, {}), cMode::Defaults))
{ {
error << "Failed to create backups directory on server" << std::endl; error << "Failed to create backups directory on server" << std::endl;
return false; return false;
} }
// Create backups directory locally if it doesn't exist // Create backups directory locally if it doesn't exist
std::string local_backups_dir = gConfig().get_local_backup_path(); std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty()) if (local_backups_dir.empty())
{ {
error << "Error: Local backups directory not found" << std::endl; error << "Error: Local backups directory not found" << std::endl;
@ -129,7 +127,7 @@ namespace dropshell
ASSERT(3 == count_substring(magic_string(), local_backup_file_path), "Invalid backup filename"); ASSERT(3 == count_substring(magic_string(), local_backup_file_path), "Invalid backup filename");
{ // Run backup script { // Run backup script
shared_commands::cRemoteTempFolder remote_temp_folder(server_env); shared_commands::cRemoteTempFolder remote_temp_folder(server_env, user);
if (!server_env.run_remote_template_command(service, command, {}, false, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) if (!server_env.run_remote_template_command(service, command, {}, false, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}}))
{ {
error << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; error << "Backup script failed on remote server: " << remote_backup_file_path << std::endl;
@ -137,7 +135,7 @@ namespace dropshell
} }
// Copy backup file from server to local // Copy backup file from server to local
if (!shared_commands::scp_file_from_remote(server_env, remote_backup_file_path, local_backup_file_path, false)) if (!shared_commands::scp_file_from_remote(server_env, remote_backup_file_path, local_backup_file_path, false, sinfo.user))
{ {
error << "Failed to copy backup file from server" << std::endl; error << "Failed to copy backup file from server" << std::endl;
return false; return false;

View File

@ -0,0 +1,63 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "version.hpp"
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/assert.hpp"
namespace dropshell {
void create_server_autocomplete(const CommandContext& ctx);
int create_server_handler(const CommandContext& ctx);
static std::vector<std::string> create_server_name_list={"create-server"};
// Static registration
struct CreateServerCommandRegister {
CreateServerCommandRegister() {
CommandRegistry::instance().register_command({
create_server_name_list,
create_server_handler,
create_server_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (after command)
1, // max_args (after command)
"create-server [SERVER]",
"Create a new server entry on this host.",
// heredoc
R"(
Create a new server entry on this host.
Note you will need to use ds install SERVER to prepare the service for use.
create-server SERVER
)"
});
}
} create_server_command_register;
void create_server_autocomplete(const CommandContext& ctx) {
return; // can't autocomplete as it's a new server!
}
int create_server_handler(const CommandContext& ctx) {
// create a new server entry on this host
if (ctx.args.size() == 0) {
error << "No server name provided" << std::endl;
return 1;
}
bool ok = create_server(ctx.args[0]);
return ok ? 0 : 1;
}
} // namespace dropshell

View File

@ -67,96 +67,206 @@ namespace dropshell
namespace shared_commands namespace shared_commands
{ {
bool print_readme(const template_info &tinfo, std::string server, std::string service)
bool print_readme(const template_info &tinfo, std::string server, std::string service)
{
std::vector<std::string> variants_to_try = {"README.txt", "readme.txt", "ReadMe.txt", "README", "readme", "README.md", "readme.md"};
std::filesystem::path readme_path = tinfo.local_template_path();
for (const auto &variant : variants_to_try)
{ {
if (std::filesystem::exists(readme_path / variant)) std::vector<std::string> variants_to_try = {"README.txt", "readme.txt", "ReadMe.txt", "README", "readme", "README.md", "readme.md"};
std::filesystem::path readme_path = tinfo.local_template_path();
for (const auto &variant : variants_to_try)
{ {
readme_path = readme_path / variant; if (std::filesystem::exists(readme_path / variant))
break; {
readme_path = readme_path / variant;
break;
}
} }
}
if (!std::filesystem::exists(readme_path)) if (!std::filesystem::exists(readme_path))
return false; return false;
std::map<std::string, std::string> all_env_vars; std::map<std::string, std::string> all_env_vars;
get_all_service_env_vars(server, service, all_env_vars); get_all_service_env_vars(server, service, all_env_vars);
all_env_vars["LOCAL_CONFIG_PATH"] = localpath::service(server, service); all_env_vars["LOCAL_CONFIG_PATH"] = localpath::service(server, service);
all_env_vars["LOCAL_TEMPLATE_PATH"] = tinfo.local_template_path().string(); all_env_vars["LOCAL_TEMPLATE_PATH"] = tinfo.local_template_path().string();
info << std::endl;
std::ifstream readme_file(readme_path);
std::string line;
while (std::getline(readme_file, line))
{
rawout << substitute_provided_key_value_pairs(line, all_env_vars) << std::endl;
}
return true;
}
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name)
{
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())
{
error << "Couldn't locate server " << server_name << " in any config directory" << std::endl;
info << "Please check the server name is correct and try again" << std::endl;
info << "You can list all servers with 'dropshell servers'" << std::endl;
info << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
return false;
}
if (std::filesystem::exists(service_dir))
{
error << "Service already exists: " << service_name << std::endl;
debug << "Current service path: " << service_dir << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(template_name);
if (!tinfo.is_set())
{
error << "Template '" << template_name << "' not found" << std::endl;
info << "Please check the template name is correct and try again" << std::endl;
info << "You can list all templates with 'dropshell templates'" << std::endl;
info << "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()))
{
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);
info << "Service " << service_name << " created successfully" << std::endl;
if (!print_readme(tinfo, server_name, service_name))
{
info << std::endl; info << std::endl;
info << "To complete the installation, please:" << std::endl; std::ifstream readme_file(readme_path);
info << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl; std::string line;
info << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl; while (std::getline(readme_file, line))
{
rawout << substitute_provided_key_value_pairs(line, all_env_vars) << std::endl;
}
return true;
}
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override/*=""*/)
{
if (server_name.empty() || template_name.empty() || service_name.empty())
return false;
ServerConfig server_info(server_name);
if (!server_info.is_valid())
{
error << "Server " << server_name << " is not valid" << std::endl;
return false;
}
std::string service_dir = localpath::service(server_name, service_name);
if (service_dir.empty())
{
error << "Couldn't locate server " << server_name << " in any config directory" << std::endl;
info << "Please check the server name is correct and try again" << std::endl;
info << "You can list all servers with 'dropshell servers'" << std::endl;
info << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
return false;
}
if (std::filesystem::exists(service_dir))
{
error << "Service already exists: " << service_name << std::endl;
debug << "Current service path: " << service_dir << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(template_name);
if (!tinfo.is_set())
{
error << "Template '" << template_name << "' not found" << std::endl;
info << "Please check the template name is correct and try again" << std::endl;
info << "You can list all templates with 'dropshell templates'" << std::endl;
info << "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()))
{
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);
// append TEMPLATE_HASH to the .template_info.env file
std::string template_info_env_file = localfile::template_info_env(server_name,service_name);
ASSERT(std::filesystem::exists(template_info_env_file), "Template info env file not found: " + template_info_env_file);
std::ofstream template_info_env_file_out(template_info_env_file, std::ios::app); // append to the file.
template_info_env_file_out << "TEMPLATE_HASH=" << tinfo.hash() << std::endl;
template_info_env_file_out.close();
// modify the SSH_USER to be nice.
// everything is created, so we can get the service info.
LocalServiceInfo service_info = get_service_info(server_name, service_name);
std::string sshuser = "root";
if (!user_override.empty())
sshuser = user_override;
else
if (!service_info.requires_host_root)
{ // find a non-root user.
auto users = server_info.get_users();
auto it = std::find_if(users.begin(), users.end(), [&sshuser](const UserConfig &user)
{ return user.user != "root"; });
if (it != users.end())
sshuser = it->user;
}
if (sshuser == "root" && !server_info.hasRootUser())
{
error << "Server " << server_name << " does not have a root user, but the service " << service_name << " requires it." << std::endl;
return false;
}
if (sshuser != "root" && service_info.requires_host_root)
{
error << "The service " << service_name << " requires a root user, but a non-root user was specified." << std::endl;
return false;
}
if (!server_info.hasUser(sshuser))
{
error << "User " << sshuser << "is not available on server " << server_name << std::endl;
return false;
}
info << "Setting SSH_USER to " << sshuser << " in the " << filenames::service_env << " file" << std::endl;
{ // edit the service.env file to set the SSH_USER.
std::string template_service_env_file = tinfo.local_template_path() / "config" / filenames::service_env;
ASSERT(std::filesystem::exists(template_service_env_file), "Template service env file not found: " + template_service_env_file);
std::ifstream template_service_env_file_in(template_service_env_file);
std::ofstream service_env_file_out(localfile::template_info_env(server_name, service_name));
std::string line;
while (std::getline(template_service_env_file_in, line))
{
if (line.find("SSH_USER") != std::string::npos)
line = "SSH_USER=" + sshuser;
service_env_file_out << line << std::endl;
}
template_service_env_file_in.close();
service_env_file_out.close();
}
// check docker.
if (service_info.requires_docker)
{
if (!server_info.hasDocker())
{
error << "Server " << server_name << " does not have docker, but the service " << service_name << " requires it." << std::endl;
return false;
}
if (service_info.requires_docker_root)
{
if (!server_info.hasRootDocker())
{
error << "Server " << server_name << " does not have a root docker, but the service " << service_name << " requires it." << std::endl;
return false;
}
}
}
info << "Service " << service_name << " created successfully" << std::endl;
if (!print_readme(tinfo, server_name, service_name))
{
info << std::endl;
info << "To complete the installation, please:" << std::endl;
info << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl;
info << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
}
return true;
}
bool merge_updated_service_template(const std::string &server_name, const std::string &service_name)
{
LocalServiceInfo service_info = get_service_info(server_name, service_name);
ASSERT(SIvalid(service_info), "Service info is not valid for " + service_name + " on " + server_name);
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
ASSERT(tinfo.is_set(), "Failed to load template " + service_info.template_name);
// copy across .template_info.env file
std::string template_info_env_file = tinfo.local_template_path() / "config" / filenames::template_info_env;
std::string target_template_info_env_file = localfile::template_info_env(server_name, service_name);
ASSERT(std::filesystem::exists(template_info_env_file), "Template service env file not found: " + template_info_env_file);
std::filesystem::remove(target_template_info_env_file);
std::filesystem::copy(template_info_env_file, target_template_info_env_file);
#pragma message("TODO: merge the template info env file")
// update hash in template info env file
// append TEMPLATE_HASH to the .template_info.env file
ASSERT(std::filesystem::exists(target_template_info_env_file), "Template info env file not found: " + target_template_info_env_file);
std::ofstream template_info_env_file_out(target_template_info_env_file, std::ios::app); // append to the file.
template_info_env_file_out << "TEMPLATE_HASH=" << tinfo.hash() << std::endl;
template_info_env_file_out.close();
return true;
} }
return true;
}
} // namespace shared_commands } // namespace shared_commands

View File

@ -0,0 +1,63 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "version.hpp"
#include "utils/assert.hpp"
#include "templates.hpp"
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
namespace dropshell {
void create_template_autocomplete(const CommandContext& ctx);
int create_template_handler(const CommandContext& ctx);
static std::vector<std::string> create_template_name_list={"create-template"};
// Static registration
struct CreateTemplateCommandRegister {
CreateTemplateCommandRegister() {
CommandRegistry::instance().register_command({
create_template_name_list,
create_template_handler,
create_template_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (after command)
1, // max_args (after command)
"create-template TEMPLATE",
"Create a new template.",
// heredoc
R"(
Create a new template.
create-template TEMPLATE
)"
});
}
} create_template_command_register;
void create_template_autocomplete(const CommandContext& ctx) {
return; // can't autocomplete as it's a new server!
}
int create_template_handler(const CommandContext& ctx) {
// create a new server entry on this host
if (ctx.args.size() == 0) {
error << "No template name provided" << std::endl;
return 1;
}
bool ok = gTemplateManager().create_template(ctx.args[0]);
return ok ? 0 : 1;
}
} // namespace dropshell

View File

@ -2,7 +2,7 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "config.hpp" #include "config.hpp"
#include "services.hpp" #include "services.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "templates.hpp" #include "templates.hpp"
@ -14,7 +14,7 @@ namespace dropshell
{ {
int nuke_handler(const CommandContext &ctx); int nuke_handler(const CommandContext &ctx);
static std::vector<std::string> nuke_name_list = {"nuke"}; static std::vector<std::string> nuke_name_list = {"destroy","nuke"};
// Static registration // Static registration
struct NukeCommandRegister struct NukeCommandRegister
@ -29,15 +29,15 @@ namespace dropshell
true, // requires_install true, // requires_install
2, // min_args (after command) 2, // min_args (after command)
2, // max_args (after command) 2, // max_args (after command)
"nuke SERVER SERVICE|all", "destroy SERVER SERVICE|all",
"Nuke a service on a server. Destroys everything, both local and remote!", "Destroy a service on a server. Erases everything, both local and remote!",
// heredoc // heredoc
R"( R"(
Nuke a service. Nuke a service.
Examples: Examples:
nuke SERVER SERVICE nuke the given service on the given server. destroy SERVER SERVICE destroy the given service on the given server.
nuke SERVER all nuke all services on the given server. destroy SERVER all destroy all services on the given server.
Note: This command is destructive and will destroy all data and all configuration, Note: This command is destructive and will destroy all data and all configuration,
both on the dropshell host and on the remote server. both on the dropshell host and on the remote server.
@ -52,7 +52,7 @@ namespace dropshell
bool nuke_service(const std::string &server, const std::string &service) bool nuke_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
// step 1 - nuke on remote server. // step 1 - nuke on remote server.
if (server_env.is_valid()) if (server_env.is_valid())
@ -63,7 +63,8 @@ namespace dropshell
if (!SIvalid(service_info)) if (!SIvalid(service_info))
error << "Invalid service: " << service << std::endl; error << "Invalid service: " << service << std::endl;
if (server_env.check_remote_dir_exists(remotepath::service(server, service))) std::string user = server_env.get_user_for_service(service);
if (server_env.check_remote_dir_exists(remotepath(server, user).service(service), user))
{ {
// run the nuke script on the remote server if it exists. // run the nuke script on the remote server if it exists.
// otherwise just uninstall. // otherwise just uninstall.
@ -82,16 +83,16 @@ namespace dropshell
} }
// Remove the service directory from the server, running in a docker container as root. // Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), true)) if (server_env.remove_remote_dir(remotepath(server, user).service(service), true, user))
{ {
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall"); ASSERT(!server_env.check_remote_dir_exists(remotepath(server, user).service(service), user), "Service directory still found on server after uninstall");
info << "Remote service directory removed: " << remotepath::service(server, service) << std::endl; info << "Remote service directory removed: " << remotepath(server, user).service(service) << std::endl;
} }
else else
warning << "Failed to remove remote service directory" << std::endl; warning << "Failed to remove remote service directory" << std::endl;
} }
else else
warning << "Service not found on remote server: " << remotepath::service(server, service) << std::endl; warning << "Service not found on remote server: " << remotepath(server, user).service(service) << std::endl;
} }
else else
warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl; warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;

View File

@ -3,7 +3,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "transwarp.hpp" #include "transwarp.hpp"

View File

@ -45,7 +45,7 @@ struct HelpCommandRegister {
void help_autocomplete(const CommandContext& ctx) { void help_autocomplete(const CommandContext& ctx) {
if (ctx.args.size() == 1) { if (ctx.args.size() == 0) {
// list all commands // list all commands
for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) { for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) {
rawout << cmd << std::endl; rawout << cmd << std::endl;
@ -148,6 +148,7 @@ int help_handler(const CommandContext& ctx) {
info << std::endl; info << std::endl;
show_command("ssh"); show_command("ssh");
info << std::endl; info << std::endl;
show_command("create-server");
show_command("create-service"); show_command("create-service");
} }
return 0; return 0;

View File

@ -40,7 +40,7 @@ namespace dropshell
0, // min_args (after command) 0, // min_args (after command)
2, // max_args (after command) 2, // max_args (after command)
"install [SERVER] [SERVICE|all]", "install [SERVER] [SERVICE|all]",
"Install/reinstall host, remote servers, or service(s). Safe/non-destructive way to update.", "Install/reinstall host and remote servers, or service(s). Safe way to update.",
// heredoc // heredoc
R"( R"(
Install components on a server. This is safe to re-run (non-destructive) and used to update Install components on a server. This is safe to re-run (non-destructive) and used to update
@ -63,15 +63,27 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND // install service over ssh : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
bool install_service(const std::string &server, const std::string &service) bool install_service(const ServerConfig &server_env, const std::string &service)
{ {
LocalServiceInfo service_info = get_service_info(server, service); std::string server = server_env.get_server_name();
if (!SIvalid(service_info)) LocalServiceInfo service_info = get_service_info(server_env.get_server_name(), service);
return false;
server_env_manager server_env(server); if (!SIvalid(service_info) || !service_info.service_template_hash_match)
if (!server_env.is_valid()) {
return false; warning << "Service " << service << " is using an old template. Updating. " << std::endl;
if (!merge_updated_service_template(server_env.get_server_name(), service))
{
error << "Failed to merge updated service template. " << std::endl;
return false;
}
service_info = get_service_info(server_env.get_server_name(), service);
if (!SIvalid(service_info) || !service_info.service_template_hash_match)
{
error << "Merged updated service template, but it is still not valid. " << std::endl;
return false;
}
}
maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server); maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
@ -90,9 +102,10 @@ namespace dropshell
} }
// Create service directory // Create service directory
std::string remote_service_path = remotepath::service(server, service); std::string user = server_env.get_user_for_service(service);
std::string remote_service_path = remotepath(server,user).service(service);
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path); std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", mkdir_cmd, {}), cMode::Silent))
{ {
std::cerr << "Failed to create service directory " << remote_service_path << std::endl; std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
return false; return false;
@ -100,7 +113,7 @@ namespace dropshell
// Check if rsync is installed on remote host // Check if rsync is installed on remote host
std::string check_rsync_cmd = "which rsync"; std::string check_rsync_cmd = "which rsync";
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent)) if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", check_rsync_cmd, {}), cMode::Silent))
{ {
std::cerr << "rsync is not installed on the remote host" << std::endl; std::cerr << "rsync is not installed on the remote host" << std::endl;
return false; return false;
@ -108,9 +121,9 @@ namespace dropshell
// Copy template files // Copy template files
debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_template(server, service) << "/" << std::endl; << std::string(8, ' ') << "[REMOTE] " << remotepath(server,user).service_template(service) << "/" << std::endl;
if (!shared_commands::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(server,user).service_template(service),
server_env, false)) server_env, false, service_info.user))
{ {
std::cerr << "Failed to copy template files using rsync" << std::endl; std::cerr << "Failed to copy template files using rsync" << std::endl;
return false; return false;
@ -118,9 +131,9 @@ namespace dropshell
// Copy service files // Copy service files
debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_config(server, service) << std::endl; << std::string(8, ' ') << "[REMOTE] " << remotepath(server,user).service_config(service) << std::endl;
if (!shared_commands::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(server,user).service_config(service),
server_env, false)) server_env, false, service_info.user))
{ {
std::cerr << "Failed to copy service files using rsync" << std::endl; std::cerr << "Failed to copy service files using rsync" << std::endl;
return false; return false;
@ -238,69 +251,56 @@ namespace dropshell
{ {
maketitle("Installing dropshell agent on this computer..."); maketitle("Installing dropshell agent on this computer...");
std::vector<std::filesystem::path> paths = { // clear out old cruft.
gConfig().get_local_template_cache_path(), std::filesystem::remove_all(localpath::agent_local());
gConfig().get_local_backup_path(), std::filesystem::remove_all(localpath::agent_remote());
gConfig().get_local_tempfiles_path(),
localpath::agent()};
for (auto &p : gConfig().get_local_server_definition_paths())
paths.push_back(p);
for (auto &p : paths) // recreate the directories.
if (!std::filesystem::exists(p)) localpath::create_directories();
{
info << "Creating directory: " << p << std::endl;
std::filesystem::create_directories(p);
}
// create the agent-local directory. // populate the agent-local directory.
recreate_agent_local::recreate_tree(localpath::agent()); recreate_agent_local::recreate_tree(localpath::agent_local());
// run the local agent installer. // run the local agent installer.
execute_local_command(localpath::agent(), "agent-install.sh",{}, nullptr, cMode::Defaults | cMode::NoBB64); execute_local_command(localpath::agent_local(), "agent-install.sh",{}, nullptr, cMode::Defaults | cMode::NoBB64);
// create the agent-remote directory. // populate the agent-remote directory.
info << "Creating local files to copy to remote agents..." << std::endl; info << "Creating local files to copy to remote agents..." << std::endl;
recreate_agent_remote::recreate_tree(localpath::files_for_remote_agent()); recreate_agent_remote::recreate_tree(localpath::agent_remote());
return 0; return 0;
} }
int install_server(const std::string &server) int install_server(const ServerConfig &server)
{ {
// install the dropshell agent on the given server. // install the dropshell agent on the given server.
maketitle("Installing dropshell agent on " + server, sColour::INFO); maketitle("Installing dropshell agent on " + server.get_server_name(), sColour::INFO);
std::string agent_path = remotepath::agent(server); for (const auto &user : server.get_users())
if (agent_path.empty())
{ {
error << "Failed to get agent path for " << server << std::endl; info << "Installing agent for user " << user.user << " on " << server.get_server_name() << std::endl;
return 1;
std::string agent_path = remotepath(server.get_server_name(),user.user).agent();
ASSERT(agent_path == user.dir+"/agent", "Agent path does not match user directory for "+user.user+"@" + server.get_server_name() + " : " + agent_path + " != " + user.dir);
ASSERT(!agent_path.empty(), "Agent path is empty for " + user.user + "@" + server.get_server_name());
// now create the agent.
// copy across from the local agent files.
info << "Copying local agent files to remote server... " << std::flush;
shared_commands::rsync_tree_to_remote(localpath::agent_remote(), agent_path, server, false, user.user);
info << "done." << std::endl;
// run the agent installer. Can't use BB64 yet, as we're installing it on the remote server.
bool okay = execute_ssh_command(server.get_SSH_INFO(user.user), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr);
if (!okay)
{
error << "ERROR: Failed to install remote agent on " << server.get_server_name() << std::endl;
return 1;
}
info << "Installation on " << server.get_server_name() << " complete." << std::endl;
} }
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Invalid server environment for " << server << std::endl;
return 1;
}
// now create the agent.
// copy across from the local agent files.
info << "Copying local agent files to remote server... " << std::flush;
shared_commands::rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false);
info << "done." << std::endl;
// run the agent installer. Can't use BB64 yet, as we're installing it on the remote server.
bool okay = execute_ssh_command(server_env.get_SSH_INFO(), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr);
if (!okay)
{
error << "ERROR: Failed to install remote agent on " << server << std::endl;
return 1;
}
info << "Installation on " << server << " complete." << std::endl;
return 0; return 0;
} }
@ -321,10 +321,10 @@ namespace dropshell
return rval; return rval;
// install the dropshell agent on all servers. // install the dropshell agent on all servers.
std::vector<ServerInfo> servers = get_configured_servers(); std::vector<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
rval = install_server(server.name); rval = install_server(server);
if (rval != 0) if (rval != 0)
return rval; return rval;
} }
@ -357,15 +357,24 @@ namespace dropshell
} }
// install service(s) // install service(s)
if (!server_exists(server))
{
error << "Server " << server << " does not exist." << std::endl;
info << "Create it with: dropshell create-server " << server << std::endl;
return 1;
}
ServerConfig server_env(server);
ASSERT(server_env.is_valid(), "Invalid server environment for " + server);
if (safearg(ctx.args, 1) == "all") if (safearg(ctx.args, 1) == "all")
{ {
// install all services on the server // install all services on the server
maketitle("Installing all services on " + server); maketitle("Installing all services on " + server);
bool okay = true; bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server); std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services) for (const auto &lsi : services)
{ {
if (!shared_commands::install_service(server, service.service_name)) if (!shared_commands::install_service(server_env, lsi.service_name))
okay = false; okay = false;
} }
return okay ? 0 : 1; return okay ? 0 : 1;
@ -373,7 +382,7 @@ namespace dropshell
else else
{ // install the specific service. { // install the specific service.
std::string service = safearg(ctx.args, 1); std::string service = safearg(ctx.args, 1);
return shared_commands::install_service(server, service) ? 0 : 1; return shared_commands::install_service(server_env, service) ? 0 : 1;
} }
} }

View File

@ -6,7 +6,7 @@
#include "servers.hpp" #include "servers.hpp"
#include "tableprint.hpp" #include "tableprint.hpp"
#include "transwarp.hpp" #include "transwarp.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include <unistd.h> #include <unistd.h>
@ -81,32 +81,63 @@ void list_servers() {
} }
tableprint tp("All DropShell Servers"); tableprint tp("All DropShell Servers");
tp.add_row({"Name", "User", "Address", "Health", "Ports"}); tp.add_row({"Name", "Address", "User", "Health", "Ports"});
info << "Checking "<<servers.size() << " servers: " << std::flush;
typedef std::map<std::string, shared_commands::ServiceStatus> tServiceStatusMap;
std::vector<tServiceStatusMap> service_status_maps;
typedef struct {dropshell::ServerConfig server; dropshell::UserConfig user;} server_user_pair;
std::vector<server_user_pair> server_user_pairs;
for (const auto& server : servers)
for (const auto& user : server.get_users())
server_user_pairs.push_back({server, user});
// mutex for the tableprint
std::mutex tp_mutex;
info << "Checking "<<server_user_pairs.size() << " agents: " << std::flush;
int checked = 0; int checked = 0;
transwarp::parallel exec{servers.size()};
auto task = transwarp::for_each(exec, servers.begin(), servers.end(), [&](const ServerInfo& server) {
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server.name);
std::set<int> ports_used; transwarp::parallel exec{server_user_pairs.size()};
auto task = transwarp::for_each(exec, server_user_pairs.begin(), server_user_pairs.end(), [&](const server_user_pair& sup) {
ServerConfig server_env(sup.server.get_server_name());
if (!server_env.is_valid())
{
error << "Invalid server environment for " << sup.server.get_server_name() << std::endl;
return;
}
std::string serviceticks = ""; std::string serviceticks = "";
std::string ports_used_str = "";
std::set<int> ports_used;
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(sup.server.get_server_name(),sup.user.user);
for (const auto& [service_name, service_status] : status) { for (const auto& [service_name, service_status] : status) {
ports_used.insert(service_status.ports.begin(), service_status.ports.end()); ports_used.insert(service_status.ports.begin(), service_status.ports.end());
serviceticks += shared_commands::HealthStatus2String(service_status.health) + " "; serviceticks += shared_commands::HealthStatus2String(service_status.health) + " ";
} }
std::string ports_used_str = "";
for (const auto& port : ports_used) for (const auto& port : ports_used)
ports_used_str += std::to_string(port) + " "; ports_used_str += std::to_string(port) + " ";
// critical section
tp.add_row({server.name, server.ssh_user, server.ssh_host, serviceticks, ports_used_str}); {
++checked; std::lock_guard<std::mutex> lock(tp_mutex);
// print out a tick character for each server checked. tp.add_row({sup.server.get_server_name(), sup.server.get_SSH_HOST(), sup.user.user, serviceticks, ports_used_str});
info << checked << "" << std::flush;
++checked;
// print out a tick character for each server checked.
info << checked << "" << std::flush;
}
}); });
task->wait(); task->wait();
info << std::endl << std::endl; info << std::endl << std::endl;
tp.sort({0,2});
tp.print(); tp.print();
} }
@ -114,7 +145,7 @@ void list_servers() {
void show_server_details(const std::string& server_name) { void show_server_details(const std::string& server_name) {
server_env_manager env(server_name); ServerConfig env(server_name);
if (!env.is_valid()) { if (!env.is_valid()) {
error << "Error: Invalid server environment file: " << server_name << std::endl; error << "Error: Invalid server environment file: " << server_name << std::endl;
return; return;
@ -122,28 +153,20 @@ void show_server_details(const std::string& server_name) {
//--------------------- //---------------------
// Check if server is reachable via SSH // Check if server is reachable via SSH
std::string ssh_address = env.get_SSH_HOST(); ASSERT(env.get_users().size() > 0, "No users found for server " + server_name);
std::string ssh_user = env.get_SSH_USER(); sSSHInfo sshinfo = env.get_SSH_INFO(env.get_users()[0].user);
std::string ssh_port = env.get_SSH_PORT(); ASSERT(sshinfo.valid(), "Invalid SSH info for server " + server_name);
if (!ssh_address.empty()) {
info << std::endl << "Server Status:" << std::endl; info << std::endl << "Server Status:" << std::endl;
info << std::string(40, '-') << std::endl; info << std::string(40, '-') << std::endl;
// Try to connect to the server // Try to connect to the server
std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " 'true' 2>/dev/null"; std::string cmd = "ssh -o ConnectTimeout=5 " + sshinfo.get_user() + "@" + sshinfo.get_host() + " -p " + sshinfo.get_port() + " 'true' 2>/dev/null";
int result = system(cmd.c_str()); int result = system(cmd.c_str());
if (result == 0) { if (result == 0) {
info << "Status: Online" << std::endl; info << "Status: Online" << std::endl;
} else {
// // Get uptime if possible warning << "Status: Offline" << std::endl;
// 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 {
warning << "Status: Offline" << std::endl;
}
} }
info << std::endl; info << std::endl;
@ -151,9 +174,21 @@ void show_server_details(const std::string& server_name) {
{ {
std::cout << std::endl; std::cout << std::endl;
tableprint tp("Server Configuration: " + server_name, true); tableprint tp("Server Configuration: " + server_name, true);
tp.add_row({"Key", "Value"}); tp.add_row({"Key", "Value"});
for (const auto& [key, value] : env.get_variables()) { for (const auto& [key, value] : env.get_variables()) {
tp.add_row({key, value}); if (key == "SSH_USERS")
{
int i=1;
for (const auto& user : env.get_users())
{
tp.add_row({"USERS -> USER[" + std::to_string(i) + "]", user.user});
tp.add_row({"USERS -> DIR[" + std::to_string(i) + "]", user.dir});
i++;
}
}
else
tp.add_row({key, value});
} }
tp.print(); tp.print();
} }
@ -163,7 +198,6 @@ void show_server_details(const std::string& server_name) {
{ {
tableprint tp("Services: " + server_name, false); tableprint tp("Services: " + server_name, false);
tp.add_row({"Status", "Service", "Template","Ports"}); tp.add_row({"Status", "Service", "Template","Ports"});
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server_name); std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server_name);
@ -175,8 +209,12 @@ void show_server_details(const std::string& server_name) {
std::string ports_str = ""; std::string ports_str = "";
for (const auto& port : service_status.ports) for (const auto& port : service_status.ports)
ports_str += std::to_string(port) + " "; ports_str += std::to_string(port) + " ";
std::string template_name = get_service_info(server_name,service_name).template_name;
if (template_name.empty())
template_name = "Unknown";
tp.add_row({healthy, service_name, get_service_info(server_name,service_name).template_name, ports_str}); tp.add_row({healthy, service_name, template_name, ports_str});
} // end of for (const auto& service : services) } // end of for (const auto& service : services)
tp.print(); tp.print();
} // end of list services } // end of list services

View File

@ -11,7 +11,7 @@
#include "config.hpp" #include "config.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
@ -57,7 +57,7 @@ namespace dropshell
std::vector<shared_commands::cBackupFileName> get_backup_files(const std::string &server, const std::string &match_service = "", const std::string &match_template_name = "") std::vector<shared_commands::cBackupFileName> get_backup_files(const std::string &server, const std::string &match_service = "", const std::string &match_template_name = "")
{ {
std::string local_backups_dir = gConfig().get_local_backup_path(); std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{ {
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl; error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
@ -92,7 +92,7 @@ namespace dropshell
std::string service = ctx.args[1]; std::string service = ctx.args[1];
std::string backup_arg = ctx.args[2]; std::string backup_arg = ctx.args[2];
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
@ -137,7 +137,7 @@ namespace dropshell
debug << " Server: " << server << std::endl; debug << " Server: " << server << std::endl;
debug << " Service: " << service << std::endl; debug << " Service: " << service << std::endl;
std::string local_backups_dir = gConfig().get_local_backup_path(); std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{ {
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl; error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
@ -183,31 +183,33 @@ namespace dropshell
{ // create the new service { // create the new service
info << "3) Creating new service..." << std::endl; info << "3) Creating new service..." << std::endl;
if (!shared_commands::create_service(server, service_info.template_name, service)) if (!shared_commands::create_service(server, service_info.template_name, service, service_info.user))
return 1; return 1;
} }
{ // installing fresh service { // installing fresh service
info << "4) Install of fresh service..." << std::endl; info << "4) Install of fresh service..." << std::endl;
if (!shared_commands::install_service(server, service)) ServerConfig server_env(server);
if (!shared_commands::install_service(server_env, service))
return 1; return 1;
} }
{ // restore service from backup { // restore service from backup
info << "5) Restoring service data from backup..." << std::endl; info << "5) Restoring service data from backup..." << std::endl;
std::string remote_backups_dir = remotepath::backups(server); std::string user = server_env.get_user_for_service(service);
std::string remote_backups_dir = remotepath(server, user).backups();
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_details->get_filename(); std::string remote_backup_file_path = remote_backups_dir + "/" + backup_details->get_filename();
debug << "Copying backup file from local to server: " << local_backup_file_path << " -> " << remote_backup_file_path << std::endl; debug << "Copying backup file from local to server: " << local_backup_file_path << " -> " << remote_backup_file_path << std::endl;
// Copy backup file from local to server // Copy backup file from local to server
if (!shared_commands::scp_file_to_remote(server_env, local_backup_file_path, remote_backup_file_path, false)) if (!shared_commands::scp_file_to_remote(server_env, local_backup_file_path, remote_backup_file_path, false, service_info.user))
{ {
error << "Failed to copy backup file from local to server" << std::endl; error << "Failed to copy backup file from local to server" << std::endl;
return 1; return 1;
} }
shared_commands::cRemoteTempFolder remote_temp_folder(server_env); shared_commands::cRemoteTempFolder remote_temp_folder(server_env,user);
debug << "Running restore script on server: " << server << std::endl; debug << "Running restore script on server: " << server << std::endl;
debug << " BACKUP_FILE: " << remote_backup_file_path << std::endl; debug << " BACKUP_FILE: " << remote_backup_file_path << std::endl;

View File

@ -1,7 +1,7 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "utils/assert.hpp" #include "utils/assert.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "directories.hpp" #include "directories.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
@ -21,10 +21,10 @@ namespace dropshell
if (ctx.args.size() == 0) if (ctx.args.size() == 0)
{ // just the command, no args yet. { // just the command, no args yet.
// list servers // list servers
std::vector<ServerInfo> servers = get_configured_servers(); std::vector<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
rawout << server.name << std::endl; rawout << server.get_server_name() << std::endl;
} }
} }
else if (ctx.args.size() == 1) else if (ctx.args.size() == 1)
@ -54,14 +54,15 @@ namespace dropshell
bool rsync_tree_to_remote( bool rsync_tree_to_remote(
const std::string &local_path, const std::string &local_path,
const std::string &remote_path, const std::string &remote_path,
server_env_manager &server_env, const ServerConfig &server_env,
bool silent) bool silent,
std::string user)
{ {
ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync."); 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() + "' " + std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " +
quote(local_path + "/") + " " + quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(user + "@" + server_env.get_SSH_HOST() + ":" +
remote_path + "/"); remote_path + "/");
return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} }
@ -84,11 +85,12 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// cRemoteTempFolder : SHARED CLASS // cRemoteTempFolder : SHARED CLASS
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env) cRemoteTempFolder::cRemoteTempFolder(const ServerConfig &server_env, std::string user) :
mServerEnv(server_env), mUser(user)
{ {
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10); std::string p = remotepath(server_env.get_server_name(),user).temp_files() + "/" + random_alphanumeric_string(10);
std::string mkdir_cmd = "mkdir -p " + quote(p); std::string mkdir_cmd = "mkdir -p " + quote(p);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) 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; error << "Failed to create temp directory on server" << std::endl;
else else
mPath = p; mPath = p;
@ -97,7 +99,7 @@ namespace dropshell
cRemoteTempFolder::~cRemoteTempFolder() cRemoteTempFolder::~cRemoteTempFolder()
{ {
std::string rm_cmd = "rm -rf " + quote(mPath); std::string rm_cmd = "rm -rf " + quote(mPath);
execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent); execute_ssh_command(mServerEnv.get_SSH_INFO(mUser), sCommand("", rm_cmd, {}), cMode::Silent);
} }
std::string cRemoteTempFolder::path() const std::string cRemoteTempFolder::path() const
@ -108,21 +110,26 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// get_all_services_status : SHARED COMMAND // get_all_services_status : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name) std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env)
{ {
std::map<std::string, ServiceStatus> status; std::map<std::string, ServiceStatus> status;
for (const auto& user : server_env.get_users()) {
server_env_manager env(server_name); status.merge(get_all_services_status(server_env, user.user));
if (!env.is_valid())
{
error << "Invalid server environment" << std::endl;
return status;
} }
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 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)}}), std::string agentpath = remotepath(server_name,user).agent();
cMode::Silent, if (!execute_ssh_command(server_env.get_SSH_INFO(user),
&output)) sCommand(agentpath, "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", agentpath}}),
cMode::Silent,
&output))
return status; return status;
std::stringstream ss(output); std::stringstream ss(output);
@ -162,6 +169,7 @@ namespace dropshell
} }
} }
} }
return status; return status;
} }
@ -208,20 +216,22 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
HealthStatus is_healthy(const std::string &server, const std::string &service) HealthStatus is_healthy(const std::string &server, const std::string &service)
{ {
server_env_manager env(server); ServerConfig env(server);
if (!env.is_valid()) if (!env.is_valid())
{ {
error << "Server service not initialized" << std::endl; error << "Server service not initialized" << std::endl;
return HealthStatus::ERROR; return HealthStatus::ERROR;
} }
if (!env.check_remote_dir_exists(remotepath::service(server, service))) std::string user = env.get_user_for_service(service);
if (!env.check_remote_dir_exists(remotepath(server,user).service(service), user))
{ {
return HealthStatus::NOTINSTALLED; return HealthStatus::NOTINSTALLED;
} }
std::string script_path = remotepath::service_template(server, service) + "/status.sh"; std::string script_path = remotepath(server,user).service_template(service) + "/status.sh";
if (!env.check_remote_file_exists(script_path)) if (!env.check_remote_file_exists(script_path, user))
{ {
return HealthStatus::UNKNOWN; return HealthStatus::UNKNOWN;
} }
@ -299,7 +309,7 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// scp_file_to_remote : SHARED COMMAND // scp_file_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
bool scp_file_to_remote(const server_env_manager &server_env, const std::string &local_path, const std::string &remote_path, bool silent) 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()) if (!server_env.is_valid())
{ {
@ -307,14 +317,14 @@ namespace dropshell
return false; return false;
} }
ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp."); 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) + " " + server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : ""); 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)); return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// scp_file_from_remote : SHARED COMMAND // scp_file_from_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
bool scp_file_from_remote(const server_env_manager &server_env, const std::string &remote_path, const std::string &local_path, bool silent) 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()) if (!server_env.is_valid())
{ {
@ -322,7 +332,7 @@ namespace dropshell
return false; return false;
} }
ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp."); 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() + " " + server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : ""); 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)); return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} }

View File

@ -3,7 +3,7 @@
#include "servers.hpp" #include "servers.hpp"
#include "command_registry.hpp" #include "command_registry.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
namespace dropshell namespace dropshell
{ {
@ -31,23 +31,26 @@ namespace dropshell
class cRemoteTempFolder class cRemoteTempFolder
{ {
public: public:
cRemoteTempFolder(const server_env_manager &server_env); // create a temp folder on the remote server cRemoteTempFolder(const ServerConfig &server_env, std::string user); // create a temp folder on the remote server
~cRemoteTempFolder(); // delete the 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 std::string path() const; // get the path to the temp folder on the remote server
private: private:
std::string mPath; std::string mPath;
const server_env_manager &mServerEnv; const ServerConfig &mServerEnv;
std::string mUser;
}; };
bool rsync_tree_to_remote( bool rsync_tree_to_remote(
const std::string &local_path, const std::string &local_path,
const std::string &remote_path, const std::string &remote_path,
server_env_manager &server_env, const ServerConfig &server_env,
bool silent); bool silent,
std::string user);
std::string get_arch(); std::string get_arch();
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name); std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env);
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env, std::string user);
std::string healthtick(const std::string &server, const std::string &service); std::string healthtick(const std::string &server, const std::string &service);
std::string HealthStatus2String(HealthStatus status); std::string HealthStatus2String(HealthStatus status);
@ -78,23 +81,24 @@ namespace dropshell
std::string mDatetime; std::string mDatetime;
}; };
bool scp_file_to_remote(const server_env_manager &server_env, const std::string &local_path, const std::string &remote_path, bool silent); bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user);
bool scp_file_from_remote(const server_env_manager &server_env, const std::string &remote_path, const std::string &local_path, bool silent); bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user);
// defined in backupdata.cpp, used by restoredata.cpp. // defined in backupdata.cpp, used by restoredata.cpp.
bool backupdata_service(const std::string& server, const std::string& service); bool backupdata_service(const ServerConfig &server_env, const std::string& service);
// defined in uninstall.cpp // defined in uninstall.cpp
bool uninstall_service(const std::string &server, const std::string &service); bool uninstall_service(const ServerConfig &server_env, const std::string &service);
// defined in nuke.cpp // defined in nuke.cpp
bool nuke_service(const std::string &server, const std::string &service); bool nuke_service(const std::string &server, const std::string &service);
// defined in install.cpp // defined in install.cpp
bool install_service(const std::string &server, const std::string &service); bool install_service(const ServerConfig &server_env, const std::string &service);
// defined in create-service.cpp // defined in create-service.cpp
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name); bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override="");
bool merge_updated_service_template(const std::string &server_name, const std::string &service_name);
} // namespace shared_commands } // namespace shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -3,10 +3,11 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "assert.hpp"
namespace dropshell namespace dropshell
{ {
@ -24,8 +25,8 @@ namespace dropshell
ssh_handler, ssh_handler,
shared_commands::std_autocomplete, shared_commands::std_autocomplete,
false, // hidden false, // hidden
true, // requires_config true, // requires_config
true, // requires_install true, // requires_install
1, // min_args (after command) 1, // min_args (after command)
2, // max_args (after command) 2, // max_args (after command)
"ssh SERVER", "ssh SERVER",
@ -34,27 +35,26 @@ namespace dropshell
ssh SERVER SERVICE SSH into a docker container for a service. ssh SERVER SERVICE SSH into a docker container for a service.
ssh SERVER SSH into a server. ssh SERVER SSH into a server.
ssh USER@SERVER SSH into a server as a specific user.
)"}); )"});
} }
} ssh_command_register; } ssh_command_register;
bool ssh_into_server(const std::string &server, std::string user)
bool ssh_into_server(const std::string &server)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; return false;
} }
execute_ssh_command(server_env.get_SSH_INFO(), sCommand(remotepath::DROPSHELL_DIR(server), "ls --color && bash", {}), cMode::Interactive); execute_ssh_command(server_env.get_SSH_INFO(user), sCommand(remotepath(server, user).DROPSHELL_DIR(), "ls --color && bash", {}), cMode::Interactive);
return true; return true;
} }
bool ssh_into_service(const std::string &server, const std::string &service) bool ssh_into_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
@ -80,7 +80,7 @@ namespace dropshell
return false; return false;
} }
server_env.run_remote_template_command(service,"ssh",{},false,{}); // explicitly supports interactive ssh! server_env.run_remote_template_command(service, "ssh", {}, false, {}); // explicitly supports interactive ssh!
return true; return true;
} }
@ -92,18 +92,41 @@ namespace dropshell
return 1; return 1;
} }
std::string server = safearg(ctx.args, 0); // ssh into the server
if (ctx.args.size() < 2) if (ctx.args.size() < 2)
{ {
// ssh into the server std::string arg1 = safearg(ctx.args, 0);
return ssh_into_server(server) ? 0 : 1; std::string server, user;
// parse either user@server or server
if (arg1.find("@") != std::string::npos)
{
user = arg1.substr(0, arg1.find("@"));
server = arg1.substr(arg1.find("@") + 1);
}
else
{
server = arg1;
// get the first user from the server.env file, and ssh in as that user.
ServerConfig server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return 1;
}
ASSERT(server_env.get_users().size() > 0, "Server " + server + " has no users");
user = server_env.get_users()[0].user;
}
return ssh_into_server(server, user) ? 0 : 1;
}
else
{ // ssh into a service on the server.
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
return ssh_into_service(server, service) ? 0 : 1;
} }
std::string service = safearg(ctx.args, 1);
// ssh into the specific service.
return ssh_into_service(server, service) ? 0 : 1;
} }
} // namespace dropshell } // namespace dropshell

View File

@ -3,7 +3,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp" #include "utils/output.hpp"
@ -44,7 +44,7 @@ namespace dropshell
bool start_service(const std::string &server, const std::string &service) bool start_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;

View File

@ -3,7 +3,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp" #include "utils/output.hpp"
@ -44,7 +44,7 @@ namespace dropshell
bool stop_service(const std::string &server, const std::string &service) bool stop_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;

View File

@ -42,34 +42,31 @@ namespace dropshell
} uninstall_command_register; } uninstall_command_register;
namespace shared_commands { namespace shared_commands {
bool uninstall_service(const std::string &server, const std::string &service) bool uninstall_service(const ServerConfig & server_env, const std::string &service)
{ {
ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name());
std::string server = server_env.get_server_name();
maketitle("Uninstalling " + service + " on " + server); maketitle("Uninstalling " + service + " on " + server);
server_env_manager server_env(server); std::string user = server_env.get_user_for_service(service);
if (!server_env.is_valid())
{
error << "Invalid server: " << server << std::endl;
return false; // should never hit this.
}
// 2. Check if service directory exists on server // 2. Check if service directory exists on server
if (!server_env.check_remote_dir_exists(remotepath::service(server, service))) if (!server_env.check_remote_dir_exists(remotepath(server, user).service(service), user))
{ {
error << "Service is not installed: " << service << std::endl; error << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall return true; // Nothing to uninstall
} }
// 3. Run uninstall script if it exists // 3. Run uninstall script if it exists
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh"; std::string uninstall_script = remotepath(server, user).service_template(service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {})) if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Uninstall script failed, but continuing with directory removal" << std::endl; warning << "Uninstall script failed, but continuing with directory removal" << std::endl;
// 4. Remove the service directory from the server, running in a docker container as root. // 4. Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), false)) if (server_env.remove_remote_dir(remotepath(server, user).service(service), false, user))
{ {
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall"); ASSERT(!server_env.check_remote_dir_exists(remotepath(server, user).service(service), user), "Service directory still found on server after uninstall");
info << "Removed remote service directory " << remotepath::service(server, service) << std::endl; info << "Removed remote service directory " << remotepath(server, user).service(service) << std::endl;
} }
else else
warning << "Failed to remove remote service directory" << std::endl; warning << "Failed to remove remote service directory" << std::endl;

View File

@ -3,7 +3,6 @@
#include <fstream> #include <fstream>
#include "config.hpp" #include "config.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/json.hpp"
#include <filesystem> #include <filesystem>
#include "utils/execute.hpp" #include "utils/execute.hpp"
#include "output.hpp" #include "output.hpp"
@ -46,6 +45,15 @@ bool config::load_config() { // load json config file.
return true; return true;
} }
void _append(std::vector<std::string> & a, const std::vector<std::string> & b) {
if (b.empty())
return;
if (a.empty())
a = b;
else
a.insert(std::end(a), std::begin(b), std::end(b));
}
bool config::save_config(bool create_aux_directories) bool config::save_config(bool create_aux_directories)
{ {
std::string config_path = localfile::dropshell_json(); std::string config_path = localfile::dropshell_json();
@ -61,37 +69,27 @@ bool config::save_config(bool create_aux_directories)
if (!mIsConfigSet) if (!mIsConfigSet)
{ {
std::string homedir = localpath::current_user_home(); std::string homedir = localpath::current_user_home();
std::string dropshell_base = homedir + "/.dropshell"; std::string dropshell_base = homedir + "/.local/dropshell_files";
mConfig["tempfiles"] = dropshell_base + "/tmp";
mConfig["backups"] = dropshell_base + "/backups";
mConfig["template_cache"] = dropshell_base + "/template_cache"; mConfig["server_definition_paths"] = {
mConfig["template_registry_URLs"] = { dropshell_base + "/servers"
"https://templates.dropshell.app"
}; };
mConfig["template_local_paths"] = { mConfig["template_local_paths"] = {
dropshell_base + "/local_templates" dropshell_base + "/local_templates"
}; };
mConfig["template_registry_URLs"] = {
mConfig["server_definition_paths"] = { "https://templates.dropshell.app"
dropshell_base + "/servers" };
}; mConfig["template_upload_token"] = "SECRETTOKEN";
mConfig["template_upload_registry_url"] = "https://templates.dropshell.app";
mConfig["template_upload_registry_token"] = "SECRETTOKEN";
} }
config_file << mConfig.dump(4); config_file << mConfig.dump(4);
config_file.close(); config_file.close();
if (create_aux_directories) { if (create_aux_directories) {
std::vector<std::filesystem::path> paths = { std::vector<std::string> paths;
get_local_template_cache_path(), _append(paths, get_local_template_paths());
get_local_backup_path(), _append(paths, get_local_server_definition_paths());
get_local_tempfiles_path()
};
for (auto & p : get_local_server_definition_paths())
paths.push_back(p);
for (auto & p : paths) for (auto & p : paths)
if (!std::filesystem::exists(p)) if (!std::filesystem::exists(p))
{ {
@ -99,6 +97,11 @@ bool config::save_config(bool create_aux_directories)
std::filesystem::create_directories(p); std::filesystem::create_directories(p);
} }
} }
debug << "Config paths: " << std::endl;
for (auto [key,value] : mConfig.items()) {
debug << " " << key << ": " << value << std::endl;
}
return true; return true;
} }
@ -110,31 +113,20 @@ bool config::is_config_set() const
bool config::is_agent_installed() bool config::is_agent_installed()
{ {
return std::filesystem::exists(localpath::agent() + "/bb64"); return std::filesystem::exists(localfile::bb64());
}
std::string config::get_local_tempfiles_path() {
return mConfig["tempfiles"];
}
std::string config::get_local_backup_path() {
return mConfig["backups"];
}
std::string config::get_local_template_cache_path() {
return mConfig["template_cache"];
} }
std::vector<std::string> config::get_template_registry_urls() { std::vector<std::string> config::get_template_registry_urls() {
nlohmann::json template_registry_urls = mConfig["template_registry_URLs"]; nlohmann::json template_registry_urls = mConfig["template_registry_URLs"];
std::vector<std::string> urls; std::vector<std::string> urls;
for (auto &url : template_registry_urls) { for (auto &url : template_registry_urls) {
urls.push_back(url); if (url.is_string() && !url.empty())
urls.push_back(url);
} }
return urls; return urls;
} }
std::vector<std::string> config::get_template_local_paths() std::vector<std::string> config::get_local_template_paths()
{ {
nlohmann::json template_local_paths = mConfig["template_local_paths"]; nlohmann::json template_local_paths = mConfig["template_local_paths"];
std::vector<std::string> paths; std::vector<std::string> paths;
@ -147,23 +139,40 @@ std::vector<std::string> config::get_template_local_paths()
std::vector<std::string> config::get_local_server_definition_paths() { std::vector<std::string> config::get_local_server_definition_paths() {
nlohmann::json server_definition_paths = mConfig["server_definition_paths"]; nlohmann::json server_definition_paths = mConfig["server_definition_paths"];
std::vector<std::string> paths; std::vector<std::string> paths;
for (auto &path : server_definition_paths) { for (auto &path : server_definition_paths) {
if (path.is_string() && !path.empty()) if (path.is_string() && !path.empty())
paths.push_back(path); paths.push_back(path);
else
warning << "Invalid server definition path: " << path << std::endl;
} }
return paths; return paths;
} }
std::string config::get_template_upload_registry_url() { std::string config::get_server_create_path()
return mConfig["template_upload_registry_url"]; {
std::vector<std::string> paths = get_local_server_definition_paths();
if (paths.empty())
return "";
return paths[0];
} }
std::string config::get_template_upload_registry_token() { std::string config::get_template_create_path()
return mConfig["template_upload_registry_token"]; {
std::vector<std::string> paths = get_local_template_paths();
if (paths.empty())
return "";
return paths[0];
}
std::string config::get_template_upload_url()
{
std::vector<std::string> urls = get_template_registry_urls();
if (urls.empty())
return "";
return urls[0];
}
std::string config::get_template_upload_token() {
return mConfig["template_upload_token"];
} }
} // namespace dropshell } // namespace dropshell

View File

@ -2,7 +2,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "utils/json.hpp"
#define JSON_INLINE_ALL
#include "json.hpp"
namespace dropshell { namespace dropshell {
@ -17,15 +19,14 @@ class config {
bool is_config_set() const; bool is_config_set() const;
static bool is_agent_installed(); static bool is_agent_installed();
std::string get_local_tempfiles_path();
std::string get_local_backup_path();
std::string get_local_template_cache_path();
std::vector<std::string> get_template_registry_urls(); std::vector<std::string> get_template_registry_urls();
std::vector<std::string> get_template_local_paths(); std::vector<std::string> get_local_template_paths();
std::vector<std::string> get_local_server_definition_paths(); std::vector<std::string> get_local_server_definition_paths();
std::string get_template_upload_registry_url(); std::string get_server_create_path();
std::string get_template_upload_registry_token(); std::string get_template_create_path();
std::string get_template_upload_url();
std::string get_template_upload_token();
private: private:
nlohmann::json mConfig; nlohmann::json mConfig;

View File

@ -1,230 +0,0 @@
#include "server_env_manager.hpp"
#include "utils/directories.hpp"
#include "utils/utils.hpp"
#include "services.hpp"
#include "templates.hpp"
#include "utils/utils.hpp"
#include "utils/json.hpp"
#include "utils/execute.hpp"
#include <iostream>
#include <memory>
#include <filesystem>
#include <fstream>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <iostream>
#include <wordexp.h> // For potential shell-like expansion if needed
namespace dropshell {
server_env_manager::server_env_manager(const std::string& server_name) : mValid(false), mServerName(server_name) {
if (server_name.empty())
return;
// Construct the full path to server.env
std::string server_env_path = localfile::server_json(server_name);
// Check if file exists
if (!std::filesystem::exists(server_env_path)) {
std::cerr << "Server environment file not found: " + server_env_path << " for server " << server_name << std::endl;
return;
}
try {
// Use envmanager to handle the environment file
nlohmann::json server_env_json = nlohmann::json::parse(std::ifstream(server_env_path));
if (server_env_json.empty()) {
std::cerr << "Error: Failed to parse server environment file: " + server_env_path << std::endl;
return;
}
// get the variables from the json
for (const auto& var : server_env_json.items()) {
std::string value;
if (var.value().is_string())
value = var.value();
else if (var.value().is_number_integer())
value = std::to_string(var.value().get<int>());
else if (var.value().is_boolean())
value = var.value() ? "true" : "false";
else
value = var.value().dump();
mVariables[var.key()] = replace_with_environment_variables_like_bash(value);
}
// Verify required variables exist
for (const auto& var : {"SSH_HOST", "SSH_USER", "SSH_PORT", "DROPSHELL_DIR"}) {
if (mVariables.find(var) == mVariables.end()) {
// Print the variables identified in the file
std::cout << "Variables identified in the file:" << std::endl;
for (const auto& v : mVariables) {
std::cout << " " << v.first << std::endl;
}
throw std::runtime_error("Missing required variable: " + std::string(var));
}
}
mValid = true;
} catch (const std::exception& e) {
std::cerr << "Failed to parse server environment file: " + std::string(e.what()) << std::endl;
}
}
bool server_env_manager::create_server_env(const std::string &server_env_path, const std::string &SSH_HOST, const std::string &SSH_USER, const std::string &SSH_PORT, const std::string &DROPSHELL_DIR)
{
nlohmann::json server_env_json;
server_env_json["SSH_HOST"] = SSH_HOST;
server_env_json["SSH_USER"] = SSH_USER;
server_env_json["SSH_PORT"] = SSH_PORT;
server_env_json["DROPSHELL_DIR"] = DROPSHELL_DIR;
try {
std::ofstream server_env_file(server_env_path);
server_env_file << server_env_json.dump(4);
server_env_file.close();
return true;
} catch (const std::exception& e) {
std::cerr << "Failed to create server environment file: " + std::string(e.what()) << std::endl;
return false;
}
}
std::string server_env_manager::get_variable(const std::string& name) const {
auto it = mVariables.find(name);
if (it == mVariables.end()) {
return "";
}
return it->second;
}
std::optional<sCommand> server_env_manager::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const
{
if (command.empty())
return std::nullopt;
std::string remote_service_template_path = remotepath::service_template(mServerName,service_name);
std::string script_path = remote_service_template_path + "/" + command + ".sh";
std::map<std::string, std::string> env_vars;
if (!get_all_service_env_vars(mServerName, service_name, env_vars)) {
std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl;
return std::nullopt;
}
std::string argstr = "";
for (const auto& arg : args) {
argstr += " " + quote(dequote(trim(arg)));
}
sCommand sc(
remote_service_template_path,
quote(script_path) + argstr + (silent ? " > /dev/null 2>&1" : ""),
env_vars
);
if (sc.empty()) {
std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl;
return std::nullopt;
}
return sc;
}
bool server_env_manager::check_remote_dir_exists(const std::string &dir_path) const
{
sCommand scommand("", "test -d " + quote(dir_path),{});
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
}
bool server_env_manager::check_remote_file_exists(const std::string& file_path) const {
sCommand scommand("", "test -f " + quote(file_path),{});
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
}
bool server_env_manager::check_remote_items_exist(const std::vector<std::string> &file_paths) const
{
// convert file_paths to a single string, separated by spaces
std::string file_paths_str;
std::string file_names_str;
for (const auto& file_path : file_paths) {
file_paths_str += quote(file_path) + " ";
file_names_str += std::filesystem::path(file_path).filename().string() + " ";
}
// check if all items in the vector exist on the remote server, in a single command.
sCommand scommand("", "for item in " + file_paths_str + "; do test -f $item; done",{});
bool okay = execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
if (!okay) {
std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl;
return false;
}
return true;
}
bool server_env_manager::remove_remote_dir(const std::string &dir_path, bool silent) const
{
std::filesystem::path path(dir_path);
std::filesystem::path parent_path = path.parent_path();
std::string target_dir = path.filename().string();
if (parent_path.empty())
parent_path="/";
if (target_dir.empty())
return false;
if (!silent)
std::cout << "Removing remote directory " << target_dir << " in " << parent_path << " on " << mServerName << std::endl;
std::string remote_cmd =
"docker run --rm -v " + quote(parent_path.string()) + ":/parent " +
" alpine rm -rf \"/parent/" + target_dir + "\"";
// if (!silent)
// std::cout << "Running command: " << remote_cmd << std::endl;
sCommand scommand("", remote_cmd,{});
cMode mode = (silent ? cMode::Silent : cMode::Defaults);
return execute_ssh_command(get_SSH_INFO(), scommand, mode);
}
bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const
{
auto scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
for (const auto& [key, value] : extra_env_vars)
scommand->add_env_var(key, value);
if (scommand->get_command_to_run().empty())
return false;
cMode mode = (command=="ssh") ? (cMode::Interactive) : (silent ? cMode::Silent : cMode::Defaults);
return execute_ssh_command(get_SSH_INFO(), scommand.value(), mode);
}
bool server_env_manager::run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command, std::vector<std::string> args, std::string &output, bool silent, std::map<std::string, std::string> extra_env_vars) const
{
auto scommand = construct_standard_template_run_cmd(service_name, command, args, false);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
for (const auto& [key, value] : extra_env_vars)
scommand->add_env_var(key, value);
return execute_ssh_command(get_SSH_INFO(), scommand.value(), cMode::Defaults, &output);
}
// base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh"
// echo YmFzaCAtYyAnRk9PPUJBUiBXSEVFPVlBWSBiYXNoIC4vdGVzdC5zaCcK | base64 -d | bash
} // namespace dropshell

View File

@ -1,77 +0,0 @@
// server_env.hpp
//
// read the server.env file and provide a class to access the variables
#ifndef __SERVER_ENV_HPP
#define __SERVER_ENV_HPP
#include <string>
#include <map>
#include <memory>
#include <vector>
#include "utils/execute.hpp"
#include <optional>
namespace dropshell {
class server_env_manager;
// ------------------------------------------------------------------------------------------------
// reads path / server.env and provides a class to access the variables.
// each env file is required to have the following variables:
// SSH_HOST
// SSH_USER
// SSH_PORT
// the following replacements are made in the values:
// ${USER} -> the username of the user running dropshell
class server_env_manager {
public:
server_env_manager(const std::string& server_name);
static bool create_server_env(
const std::string& server_env_path,
const std::string& SSH_HOST,
const std::string& SSH_USER,
const std::string& SSH_PORT,
const std::string& DROPSHELL_DIR);
std::string get_variable(const std::string& name) const;
// trivial getters.
const std::map<std::string, std::string>& get_variables() const { return mVariables; }
std::string get_SSH_HOST() const { return get_variable("SSH_HOST"); }
std::string get_SSH_USER() const { return get_variable("SSH_USER"); }
std::string get_SSH_PORT() const { return get_variable("SSH_PORT"); }
std::string get_DROPSHELL_DIR() const { return get_variable("DROPSHELL_DIR"); }
sSSHInfo get_SSH_INFO() const { return sSSHInfo{get_SSH_HOST(), get_SSH_USER(), get_SSH_PORT(), get_server_name()}; }
bool is_valid() const { return mValid; }
std::string get_server_name() const { return mServerName; }
// helper functions
public:
bool check_remote_dir_exists(const std::string &dir_path) const;
bool check_remote_file_exists(const std::string& file_path) const;
bool check_remote_items_exist(const std::vector<std::string>& file_paths) const;
bool remove_remote_dir(const std::string &dir_path, bool silent) const;
bool run_remote_template_command(const std::string& service_name, const std::string& command,
std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const;
bool run_remote_template_command_and_capture_output(const std::string& service_name, const std::string& command,
std::vector<std::string> args, std::string & output, bool silent, std::map<std::string, std::string> extra_env_vars) const;
private:
std::optional<sCommand> construct_standard_template_run_cmd(const std::string& service_name, const std::string& command, const std::vector<std::string> args, const bool silent) const;
private:
std::string mServerName;
std::map<std::string, std::string> mVariables;
bool mValid;
};
} // namespace dropshell
#endif // __SERVER_ENV_HPP

View File

@ -1,125 +1,453 @@
#include "servers.hpp"
#include "server_env_manager.hpp"
#include "utils/tableprint.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "utils/utils.hpp"
#include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "config.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "contrib/transwarp.hpp" #include "utils/utils.hpp"
#include "utils/execute.hpp"
#include "output.hpp"
#include "utils/assert.hpp"
#include "config.hpp"
#include <iostream> #include <iostream>
#include <fstream> #include <memory>
#include <iomanip>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <iostream>
#include <wordexp.h> // For potential shell-like expansion if needed
namespace dropshell { namespace dropshell
{
std::vector<ServerInfo> get_configured_servers() { ServerConfig::ServerConfig(const std::string &server_name) : mValid(false), mServerName(server_name)
std::vector<ServerInfo> servers; {
if (server_name.empty())
return;
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths(); std::string server_json_path = localfile::server_json(server_name);
if (lsdp.empty())
return servers;
for (auto servers_dir : lsdp) { // Check if file exists
if (!servers_dir.empty() && std::filesystem::exists(servers_dir)) { if (!std::filesystem::exists(server_json_path))
for (const auto& entry : std::filesystem::directory_iterator(servers_dir)) { {
if (std::filesystem::is_directory(entry)) { std::cerr << "Server environment file not found: " + server_json_path << " for server " << server_name << std::endl;
std::string server_name = entry.path().filename().string(); return;
}
if (server_name.empty() || server_name[0]=='.' || server_name[0]=='_') try
continue; {
// Use envmanager to handle the environment file
nlohmann::json server_env_json = nlohmann::json::parse(std::ifstream(server_json_path));
if (server_env_json.empty())
{
error << "Failed to parse server environment file at "<< server_json_path << std::endl;
info << "The returned json was empty." << std::endl;
return;
}
server_env_manager env(server_name); // get the variables from the json, converting everything to strings.
if (!env.is_valid()) { for (const auto &var : server_env_json.items())
std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl; {
continue; std::string value;
if (var.value().is_string())
value = var.value();
else if (var.value().is_number_integer())
value = std::to_string(var.value().get<int>());
else if (var.value().is_boolean())
value = var.value() ? "true" : "false";
else
value = var.value().dump();
mVariables[var.key()] = replace_with_environment_variables_like_bash(value);
}
// Verify required variables exist
for (const auto &var : {"SSH_HOST", "SSH_PORT", "SSH_USERS"})
{
if (mVariables.find(var) == mVariables.end())
{
// Print the variables identified in the file
info << "Variables identified in the file:" << std::endl;
for (const auto &v : mVariables)
{
info << " " << v.first << std::endl;
}
throw std::runtime_error("Missing required variable: " + std::string(var));
}
}
// Parse users array
if (!server_env_json.contains("SSH_USERS") || !server_env_json["SSH_USERS"].is_array())
{
error << "SSH_USERS array not found or invalid in server configuration" << std::endl;
return;
}
for (const auto &user_json : server_env_json["SSH_USERS"])
{
UserConfig user;
user.user = user_json["USER"].get<std::string>();
user.dir = user_json["DIR"].get<std::string>();
mUsers.push_back(user);
}
if (mUsers.empty())
{
error << "No users defined in server configuration " << server_json_path << std::endl;
return;
}
mValid = true;
}
catch (const std::exception &e)
{
error << "Failed to parse " << server_json_path << std::endl;
error << "Error: " << e.what() << std::endl;
mValid = false;
}
}
std::string ServerConfig::get_SSH_HOST() const
{
return get_variable("SSH_HOST");
}
std::string ServerConfig::get_SSH_PORT() const
{
return get_variable("SSH_PORT");
}
std::vector<UserConfig> ServerConfig::get_users() const
{
return mUsers;
}
std::string ServerConfig::get_user_dir(const std::string &user) const
{
for (const auto &u : mUsers)
{
if (u.user == user)
{
return u.dir;
}
}
return "";
}
std::string ServerConfig::get_server_name() const
{
return mServerName;
}
std::string ServerConfig::get_user_for_service(const std::string &service) const
{
return dropshell::get_user_for_service(mServerName, service);
}
std::string get_user_for_service(const std::string &server, const std::string &service)
{
auto services_info = get_server_services_info(server);
auto it = std::find_if(services_info.begin(), services_info.end(),
[&service](const LocalServiceInfo &si)
{ return si.service_name == service; });
if (it != services_info.end() && SIvalid(*it))
return it->user;
return "";
}
sSSHInfo ServerConfig::get_SSH_INFO(std::string user) const
{
ASSERT(!user.empty(), "User is empty, cannot get SSH info.");
// Find user in mUsers vector
auto it = std::find_if(mUsers.begin(), mUsers.end(),
[&user](const UserConfig &u)
{ return u.user == user; });
ASSERT(it != mUsers.end(), ("User " + user + " not found in server environment."));
return sSSHInfo(get_SSH_HOST(), it->user, get_SSH_PORT(), get_server_name(), it->dir);
}
bool ServerConfig::hasRootUser() const
{
auto it = std::find_if(mUsers.begin(), mUsers.end(),[](const UserConfig &u)
{ return u.user == "root"; });
return it != mUsers.end();
}
bool ServerConfig::hasDocker() const
{
return get_variable("HAS_DOCKER") == "true";
}
bool ServerConfig::hasRootDocker() const
{
return get_variable("DOCKER_ROOTLESS") == "false";
}
bool ServerConfig::hasUser(const std::string &user) const
{
auto it = std::find_if(mUsers.begin(), mUsers.end(),
[&user](const UserConfig &u)
{ return u.user == user; });
return it != mUsers.end();
}
bool ServerConfig::check_remote_dir_exists(const std::string &dir_path, std::string user) const
{
sCommand scommand("", "test -d " + quote(dir_path), {});
return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent);
}
bool ServerConfig::check_remote_file_exists(const std::string &file_path, std::string user) const
{
sCommand scommand("", "test -f " + quote(file_path), {});
return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent);
}
bool ServerConfig::check_remote_items_exist(const std::vector<std::string> &file_paths, std::string user) const
{
// convert file_paths to a single string, separated by spaces
std::string file_paths_str;
std::string file_names_str;
for (const auto &file_path : file_paths)
{
file_paths_str += quote(file_path) + " ";
file_names_str += std::filesystem::path(file_path).filename().string() + " ";
}
// check if all items in the vector exist on the remote server, in a single command.
sCommand scommand("", "for item in " + file_paths_str + "; do test -f $item; done", {});
sSSHInfo sshinfo = get_SSH_INFO(user);
bool okay = execute_ssh_command(sshinfo, scommand, cMode::Silent);
if (!okay)
{
std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl;
return false;
}
return true;
}
bool ServerConfig::remove_remote_dir(
const std::string &dir_path, bool silent, std::string user) const
{
std::filesystem::path path(dir_path);
std::filesystem::path parent_path = path.parent_path();
std::string target_dir = path.filename().string();
if (parent_path.empty())
parent_path = "/";
if (target_dir.empty())
return false;
if (!silent)
std::cout << "Removing remote directory " << target_dir << " in " << parent_path << " on " << mServerName << std::endl;
std::string remote_cmd =
"docker run --rm -v " + quote(parent_path.string()) + ":/parent " +
" alpine rm -rf \"/parent/" + target_dir + "\"";
// if (!silent)
// std::cout << "Running command: " << remote_cmd << std::endl;
sCommand scommand("", remote_cmd, {});
cMode mode = (silent ? cMode::Silent : cMode::Defaults);
sSSHInfo sshinfo = get_SSH_INFO(user);
return execute_ssh_command(sshinfo, scommand, mode);
}
bool ServerConfig::run_remote_template_command(
const std::string &service_name,
const std::string &command,
std::vector<std::string> args,
bool silent,
std::map<std::string, std::string> extra_env_vars) const
{
std::string user = get_user_for_service(service_name);
auto scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
for (const auto &[key, value] : extra_env_vars)
scommand->add_env_var(key, value);
if (scommand->get_command_to_run().empty())
return false;
cMode mode = (command == "ssh") ? (cMode::Interactive) : (silent ? cMode::Silent : cMode::Defaults);
return execute_ssh_command(get_SSH_INFO(user), scommand.value(), mode);
}
bool ServerConfig::run_remote_template_command_and_capture_output(
const std::string &service_name,
const std::string &command,
std::vector<std::string> args,
std::string &output,
bool silent,
std::map<std::string, std::string> extra_env_vars) const
{
std::string user = get_user_for_service(service_name);
auto scommand = construct_standard_template_run_cmd(service_name, command, args, false);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
for (const auto &[key, value] : extra_env_vars)
scommand->add_env_var(key, value);
return execute_ssh_command(get_SSH_INFO(user), scommand.value(), cMode::Defaults, &output);
}
std::string ServerConfig::get_variable(const std::string &name) const
{
auto it = mVariables.find(name);
if (it == mVariables.end())
{
return "";
}
return it->second;
}
std::optional<sCommand> ServerConfig::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const
{
if (command.empty())
return std::nullopt;
std::string user = get_user_for_service(service_name);
std::string remote_service_template_path = remotepath(mServerName, user).service_template(service_name);
std::string script_path = remote_service_template_path + "/" + command + ".sh";
std::map<std::string, std::string> env_vars;
if (!get_all_service_env_vars(mServerName, service_name, env_vars))
{
std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl;
return std::nullopt;
}
std::string argstr = "";
for (const auto &arg : args)
{
argstr += " " + quote(dequote(trim(arg)));
}
sCommand sc(
remote_service_template_path,
quote(script_path) + argstr + (silent ? " > /dev/null 2>&1" : ""),
env_vars);
if (sc.empty())
{
std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl;
return std::nullopt;
}
return sc;
}
std::vector<ServerConfig> get_configured_servers()
{
std::vector<ServerConfig> 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;
ServerConfig env(server_name);
if (!env.is_valid())
{
std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl;
continue;
}
servers.push_back(env);
} }
servers.push_back({
server_name,
env.get_SSH_HOST(),
env.get_SSH_USER(),
env.get_SSH_PORT()
});
} }
} }
} }
return servers;
} }
return servers; bool 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())
{
error << "Error: Server name already exists: " << server_name << std::endl;
info << "Current server path: " << server_existing_dir << std::endl;
return false;
}
ServerInfo get_server_info(const std::string &server_name) // 2. create a new directory in the user config directory
{ auto lsdp = gConfig().get_local_server_definition_paths();
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths(); if (lsdp.empty() || lsdp[0].empty())
if (lsdp.empty()) {
return ServerInfo(); error << "Error: Local server definition path not found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false;
}
std::string server_dir = lsdp[0] + "/" + server_name;
std::filesystem::create_directory(server_dir);
for (auto &config_dir : lsdp) { // 3. create a template server.env file in the server directory
std::string server_dir = config_dir + "/" + server_name; std::string user = getenv("USER");
if (std::filesystem::exists(server_dir)) { std::string server_env_path = server_dir + "/" + filenames::server_json;
server_env_manager env(server_name); std::ofstream server_env_file(server_env_path);
if (!env.is_valid()) { server_env_file << "{" << std::endl;
std::cerr << "Error: Invalid server environment file: " << server_dir << std::endl; server_env_file << " \"SSH_HOST\": \"" << server_name << "\"," << std::endl;
continue; server_env_file << " \"SSH_PORT\": " << 22 << "," << std::endl;
} server_env_file << " \"SSH_USERS\": [" << std::endl;
return ServerInfo({server_name, env.get_SSH_HOST(), env.get_SSH_USER(), env.get_SSH_PORT()}); server_env_file << " {" << std::endl;
server_env_file << " \"USER\": \"" << user << "\"," << std::endl;
server_env_file << " \"DIR\": \"" << "/home/" + user << "/.dropshell\"" << std::endl;
server_env_file << " }" << std::endl;
server_env_file << " ]" << std::endl;
server_env_file << "}" << std::endl;
server_env_file.close();
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) install the server: dropshell install " << server_name << std::endl;
std::cout << std::endl;
return true;
}
void get_all_used_commands(std::set<std::string> &commands)
{
std::vector<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers)
{
auto services = get_server_services_info(server.get_server_name());
for (const auto &service : services)
commands.merge(get_used_commands(server.get_server_name(), service.service_name));
} }
} }
return ServerInfo();
}
bool server_exists(const std::string &server_name)
bool 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 false;
}
// 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" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false;
}
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();
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 the server: dropshell install " << server_name << std::endl;
std::cout << std::endl;
return true;
}
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); std::string server_existing_dir = localpath::server(server_name);
for (const auto& service : services) if (server_existing_dir.empty())
commands.merge(dropshell::get_used_commands(server.name, service.service_name)); return false;
if (std::filesystem::exists(server_existing_dir));
return true;
return false;
} }
}
} // namespace dropshell } // namespace dropshell

View File

@ -1,28 +1,104 @@
#ifndef SERVERS_HPP // server_env.hpp
#define SERVERS_HPP //
// read the server.env file and provide a class to access the variables
#ifndef __SERVER_ENV_HPP
#define __SERVER_ENV_HPP
#include <string> #include <string>
#include <set> #include <map>
#include <memory>
#include <vector> #include <vector>
#include <set>
#include <optional>
namespace dropshell { #include "utils/execute.hpp"
// Server information structure namespace dropshell
struct ServerInfo { {
std::string name;
std::string ssh_host; struct UserConfig
std::string ssh_user; {
std::string ssh_port; std::string user;
std::string dir;
}; };
std::vector<ServerInfo> get_configured_servers(); // ------------------------------------------------------------------------------------------------
ServerInfo get_server_info(const std::string& server_name); // reads path / server.env and provides a class to access the variables.
// each env file is required to have the following variables:
// SSH_HOST
// SSH_UNPRIVILEGED_USER
// SSH_PORT
// the following replacements are made in the values:
// ${USER} -> the username of the user running dropshell
class ServerConfig
{
public:
ServerConfig(const std::string &server_name);
bool is_valid() const { return mValid; }
// trivial getters.
const std::map<std::string, std::string> &get_variables() const { return mVariables; }
std::string get_variable(const std::string &name) const;
// ------------------------------------------------------------------------------------------------
// getters
// ------------------------------------------------------------------------------------------------
std::string get_SSH_HOST() const;
std::string get_SSH_PORT() const;
std::vector<UserConfig> get_users() const;
std::string get_user_dir(const std::string &user) const;
std::string get_server_name() const;
std::string get_user_for_service(const std::string &service) const;
sSSHInfo get_SSH_INFO(std::string user) const;
// server capabilities
bool hasRootUser() const;
bool hasDocker() const;
bool hasRootDocker() const;
// helper functions
bool hasUser(const std::string &user) const;
public:
bool check_remote_dir_exists(const std::string &dir_path, std::string user) const;
bool check_remote_file_exists(const std::string &file_path, std::string user) const;
bool check_remote_items_exist(const std::vector<std::string> &file_paths, std::string user) const;
bool remove_remote_dir(const std::string &dir_path, bool silent, std::string user) const;
bool run_remote_template_command(const std::string &service_name, const std::string &command,
std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const;
bool run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command,
std::vector<std::string> args, std::string &output, bool silent, std::map<std::string, std::string> extra_env_vars) const;
private:
std::optional<sCommand> construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const;
private:
std::string mServerName;
std::map<std::string, std::string> mVariables;
std::vector<UserConfig> mUsers;
bool mValid;
}; // class ServerConfig
std::vector<ServerConfig> get_configured_servers();
std::string get_user_for_service(const std::string &server, const std::string &service);
bool create_server(const std::string &server_name);
bool create_server(const std::string& server_name);
void get_all_used_commands(std::set<std::string> &commands); void get_all_used_commands(std::set<std::string> &commands);
bool server_exists(const std::string &server_name);
} // namespace dropshell } // namespace dropshell
#endif // SERVERS_HPP #endif // __SERVER_ENV_HPP

View File

@ -1,529 +1,529 @@
#include <iostream> // #include <iostream>
#include <fstream> // #include <fstream>
#include <sstream> // #include <sstream>
#include <cstdlib> // #include <cstdlib>
#include <chrono> // #include <chrono>
#include <iomanip> // #include <iomanip>
#include <filesystem> // #include <filesystem>
#include <unistd.h> // #include <unistd.h>
#include "utils/assert.hpp" // #include "utils/assert.hpp"
#include "config.hpp" // #include "config.hpp"
#include "server_env_manager.hpp" // #include "servers.hpp"
#include "templates.hpp" // #include "templates.hpp"
#include "services.hpp" // #include "services.hpp"
#include "utils/directories.hpp" // #include "utils/directories.hpp"
#include "utils/utils.hpp" // #include "utils/utils.hpp"
#include "command_registry.hpp" // #include "command_registry.hpp"
#include "shared_commands.hpp" // #include "shared_commands.hpp"
namespace dropshell { // namespace dropshell {
class service_runner { // class service_runner {
public: // public:
service_runner(const std::string& server_name, const std::string& service_name); // service_runner(const std::string& server_name, const std::string& service_name);
bool isValid() const { return mValid; } // bool isValid() const { return mValid; }
// run a command over ssh, using the credentials from server.env (via server_env.hpp) // // run a command over ssh, using the credentials from server.env (via server_env.hpp)
// first check that the command corresponds to a valid .sh file in the service directory // // first check that the command corresponds to a valid .sh file in the service directory
// then run the command, passing the {service_name}.env file as an argument // // then run the command, passing the {service_name}.env file as an argument
// do a lot of checks, such as: // // do a lot of checks, such as:
// checking that we can ssh to the server. // // checking that we can ssh to the server.
// checking whether the service directory exists on the server. // // checking whether the service directory exists on the server.
// checking that the command exists in the service directory. // // checking that the command exists in the service directory.
// checking that the command is a valid .sh file. // // checking that the command is a valid .sh file.
// checking that the {service_name}.env file exists in the service directory. // // checking that the {service_name}.env file exists in the service directory.
bool run_command(const std::string& command, std::vector<std::string> additional_args={}, std::map<std::string, std::string> env_vars={}); // bool run_command(const std::string& command, std::vector<std::string> additional_args={}, std::map<std::string, std::string> env_vars={});
// check health of service. Silent. // // check health of service. Silent.
// 1. run status.sh on the server // // 1. run status.sh on the server
// 2. return the output of the status.sh script // // 2. return the output of the status.sh script
//HealthStatus is_healthy(); // //HealthStatus is_healthy();
// std::string healthtick(); // // std::string healthtick();
// std::string healthmark(); // // std::string healthmark();
public: // public:
// backup and restore // // backup and restore
bool backup(bool silent=false); // bool backup(bool silent=false);
bool restore(std::string backup_file, bool silent=false); // bool restore(std::string backup_file, bool silent=false);
// nuke the service // // nuke the service
bool nuke(bool silent=false); // nukes all data for this service on the remote server // bool nuke(bool silent=false); // nukes all data for this service on the remote server
bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles // bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles
// launch an interactive ssh session on a server or service // // launch an interactive ssh session on a server or service
// replaces the current dropshell process with the ssh process // // replaces the current dropshell process with the ssh process
bool interactive_ssh_service(); // bool interactive_ssh_service();
bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false); // bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false);
bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false); // bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false);
public: // public:
// utility functions // // utility functions
static std::string get_latest_backup_file(const std::string& server, const std::string& service); // static std::string get_latest_backup_file(const std::string& server, const std::string& service);
static bool interactive_ssh(const std::string & server_name, const std::string & command); // static bool interactive_ssh(const std::string & server_name, const std::string & command);
// static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name); // // static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
private: // private:
std::string mServer; // std::string mServer;
server_env_manager mServerEnv; // ServerConfig mServerEnv;
LocalServiceInfo mServiceInfo; // LocalServiceInfo mServiceInfo;
std::string mService; // std::string mService;
bool mValid; // bool mValid;
// Helper methods // // Helper methods
public: // public:
}; // };
} // namespace dropshell // } // namespace dropshell
namespace fs = std::filesystem; // namespace fs = std::filesystem;
namespace dropshell { // namespace dropshell {
service_runner::service_runner(const std::string& server_name, const std::string& service_name) : // service_runner::service_runner(const std::string& server_name, const std::string& service_name) :
mServerEnv(server_name), mServer(server_name), mService(service_name), mValid(false) // mServerEnv(server_name), mServer(server_name), mService(service_name), mValid(false)
{ // {
if (server_name.empty() || service_name.empty()) // if (server_name.empty() || service_name.empty())
return; // return;
// Initialize server environment // // Initialize server environment
if (!mServerEnv.is_valid()) // if (!mServerEnv.is_valid())
return; // return;
mServiceInfo = get_service_info(server_name, service_name); // mServiceInfo = get_service_info(server_name, service_name);
if (mServiceInfo.service_name.empty()) // if (mServiceInfo.service_name.empty())
return; // return;
mService = mServiceInfo.service_name; // mService = mServiceInfo.service_name;
mValid = !mServiceInfo.local_template_path.empty(); // mValid = !mServiceInfo.local_template_path.empty();
} // }
bool service_runner::nuke(bool silent) // bool service_runner::nuke(bool silent)
{ // {
maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer); // maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);
if (!mServerEnv.is_valid()) return false; // should never hit this. // if (!mServerEnv.is_valid()) return false; // should never hit this.
std::string remote_service_path = remotepath::service(mServer, mService); // std::string remote_service_path = remotepath::service(mServer, mService);
info << "Service " << mService << " successfully nuked from " << mServer << std::endl; // info << "Service " << mService << " successfully nuked from " << mServer << std::endl;
if (!silent) { // if (!silent) {
info << "There's nothing left on the remote server." << std::endl; // info << "There's nothing left on the remote server." << std::endl;
info << "You can remove the local files with:" << std::endl; // info << "You can remove the local files with:" << std::endl;
info << " rm -rf " << localpath::service(mServer,mService) << std::endl; // info << " rm -rf " << localpath::service(mServer,mService) << std::endl;
} // }
return true; // return true;
} // }
bool service_runner::fullnuke() // bool service_runner::fullnuke()
{ // {
if (!nuke(true)) // if (!nuke(true))
{ // {
warning << "Nuke script failed, aborting." << std::endl; // warning << "Nuke script failed, aborting." << std::endl;
return false; // return false;
} // }
std::string local_service_path = mServiceInfo.local_service_path; // std::string local_service_path = mServiceInfo.local_service_path;
if (local_service_path.empty() || !fs::exists(local_service_path)) { // if (local_service_path.empty() || !fs::exists(local_service_path)) {
error << "Service directory not found: " << local_service_path << std::endl; // error << "Service directory not found: " << local_service_path << std::endl;
return false; // return false;
} // }
std::string rm_cmd = "rm -rf " + quote(local_service_path); // std::string rm_cmd = "rm -rf " + quote(local_service_path);
if (!execute_local_command("", rm_cmd, {}, nullptr, cMode::Silent)) { // if (!execute_local_command("", rm_cmd, {}, nullptr, cMode::Silent)) {
error << "Failed to remove service directory" << std::endl; // error << "Failed to remove service directory" << std::endl;
return false; // return false;
} // }
return true; // return true;
} // }
// ------------------------------------------------------------------------------------------------ // // ------------------------------------------------------------------------------------------------
// Run a command on the service. // // Run a command on the service.
// ------------------------------------------------------------------------------------------------ // // ------------------------------------------------------------------------------------------------
bool service_runner::run_command(const std::string& command, std::vector<std::string> additional_args, std::map<std::string, std::string> env_vars) { // bool service_runner::run_command(const std::string& command, std::vector<std::string> additional_args, std::map<std::string, std::string> env_vars) {
if (!mServerEnv.is_valid()) { // if (!mServerEnv.is_valid()) {
std::cerr << "Error: Server service not initialized" << std::endl; // std::cerr << "Error: Server service not initialized" << std::endl;
return false; // return false;
} // }
template_info tinfo = gTemplateManager().get_template_info(mServiceInfo.template_name); // template_info tinfo = gTemplateManager().get_template_info(mServiceInfo.template_name);
if (!tinfo.is_set()) { // if (!tinfo.is_set()) {
std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl; // std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl;
return false; // return false;
} // }
if (command == "fullnuke") // if (command == "fullnuke")
return fullnuke(); // return fullnuke();
if (command == "nuke") // if (command == "nuke")
{ // {
std::cout << "Nuking " << mService << " (" << mServiceInfo.template_name << ") on " << mServer << std::endl; // std::cout << "Nuking " << mService << " (" << mServiceInfo.template_name << ") on " << mServer << std::endl;
return nuke(); // return nuke();
} // }
if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) { // if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) {
std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl; // std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
return true; // nothing to run. // return true; // nothing to run.
} // }
// install doesn't require anything on the server yet. // // install doesn't require anything on the server yet.
// if (command == "install") // // if (command == "install")
// return install_service(mServer, mService, false); // // return install_service(mServer, mService, false);
std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh"; // std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh";
// Check if service directory exists // // Check if service directory exists
if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) { // if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
std::cerr << "Error: Service is not installed: " << mService << std::endl; // std::cerr << "Error: Service is not installed: " << mService << std::endl;
return false; // return false;
} // }
// Check if command script exists // // Check if command script exists
if (!mServerEnv.check_remote_file_exists(script_path)) { // if (!mServerEnv.check_remote_file_exists(script_path)) {
std::cerr << "Error: Remote command script not found: " << script_path << std::endl; // std::cerr << "Error: Remote command script not found: " << script_path << std::endl;
return false; // return false;
} // }
// Check if env file exists // // Check if env file exists
if (!mServerEnv.check_remote_file_exists(remotefile::service_env(mServer, mService))) { // if (!mServerEnv.check_remote_file_exists(remotefile::service_env(mServer, mService))) {
std::cerr << "Error: Service config file not found: " << remotefile::service_env(mServer, mService) << std::endl; // std::cerr << "Error: Service config file not found: " << remotefile::service_env(mServer, mService) << std::endl;
return false; // return false;
} // }
// if (command == "uninstall") // // if (command == "uninstall")
// return uninstall(); // // return uninstall();
if (command == "ssh") { // if (command == "ssh") {
interactive_ssh_service(); // interactive_ssh_service();
return true; // return true;
} // }
if (command == "restore") { // if (command == "restore") {
if (additional_args.size() < 1) { // if (additional_args.size() < 1) {
std::cerr << "Error: restore requires a backup file:" << std::endl; // std::cerr << "Error: restore requires a backup file:" << std::endl;
std::cerr << "dropshell restore <server> <service> <backup-file>" << std::endl; // std::cerr << "dropshell restore <server> <service> <backup-file>" << std::endl;
return false; // return false;
} // }
return restore(additional_args[0], false); // return restore(additional_args[0], false);
} // }
if (command == "backup") { // if (command == "backup") {
return backup(false); // return backup(false);
} // }
// Run the generic command // // Run the generic command
std::vector<std::string> args; // not passed through yet. // std::vector<std::string> args; // not passed through yet.
return mServerEnv.run_remote_template_command(mService, command, args, false, env_vars); // return mServerEnv.run_remote_template_command(mService, command, args, false, env_vars);
} // }
bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) { // bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) {
std::string serverpath = localpath::server(server_name); // std::string serverpath = localpath::server(server_name);
if (serverpath.empty()) { // if (serverpath.empty()) {
std::cerr << "Error: Server not found: " << server_name << std::endl; // std::cerr << "Error: Server not found: " << server_name << std::endl;
return false; // return false;
} // }
server_env_manager env(server_name); // ServerConfig env(server_name);
if (!env.is_valid()) { // if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl; // std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
return false; // return false;
} // }
sCommand scommand("", "bash",{}); // sCommand scommand("", "bash",{});
return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive); // return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
} // }
bool service_runner::interactive_ssh_service() // bool service_runner::interactive_ssh_service()
{ // {
std::set<std::string> used_commands = get_used_commands(mServer, mService); // std::set<std::string> used_commands = get_used_commands(mServer, mService);
if (used_commands.find("ssh") == used_commands.end()) { // if (used_commands.find("ssh") == used_commands.end()) {
std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl; // std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
return false; // return false;
} // }
std::vector<std::string> args; // not passed through yet. // std::vector<std::string> args; // not passed through yet.
return mServerEnv.run_remote_template_command(mService, "ssh", args, false, {}); // return mServerEnv.run_remote_template_command(mService, "ssh", args, false, {});
} // }
bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent) // bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent)
{ // {
std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : ""); // std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : "");
return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); // return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} // }
bool service_runner::scp_file_from_remote(const std::string &remote_path, const std::string &local_path, bool silent) // bool service_runner::scp_file_from_remote(const std::string &remote_path, const std::string &local_path, bool silent)
{ // {
std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : ""); // std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.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)); // return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} // }
bool service_runner::restore(std::string backup_file, bool silent) // bool service_runner::restore(std::string backup_file, bool silent)
{ // {
if (backup_file.empty()) { // if (backup_file.empty()) {
std::cerr << "Error: not enough arguments. dropshell restore <server> <service> <backup-file>" << std::endl; // std::cerr << "Error: not enough arguments. dropshell restore <server> <service> <backup-file>" << std::endl;
return false; // return false;
} // }
std::string local_backups_dir = gConfig().get_local_backup_path(); // std::string local_backups_dir = gConfig().get_local_backup_path();
if (backup_file == "latest") { // if (backup_file == "latest") {
// get the latest backup file from the server // // get the latest backup file from the server
backup_file = get_latest_backup_file(mServer, mService); // backup_file = get_latest_backup_file(mServer, mService);
} // }
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string(); // std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string();
if (! std::filesystem::exists(local_backup_file_path)) { // if (! std::filesystem::exists(local_backup_file_path)) {
std::cerr << "Error: Backup file not found at " << local_backup_file_path << std::endl; // std::cerr << "Error: Backup file not found at " << local_backup_file_path << std::endl;
return false; // return false;
} // }
// split the backup filename into parts based on the magic string // // split the backup filename into parts based on the magic string
std::vector<std::string> parts = dropshell::split(backup_file, "-_-"); // std::vector<std::string> parts = dropshell::split(backup_file, "-_-");
if (parts.size() != 4) { // if (parts.size() != 4) {
std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl; // std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
return false; // return false;
} // }
std::string backup_server_name = parts[0]; // std::string backup_server_name = parts[0];
std::string backup_template_name = parts[1]; // std::string backup_template_name = parts[1];
std::string backup_service_name = parts[2]; // std::string backup_service_name = parts[2];
std::string backup_datetime = parts[3]; // std::string backup_datetime = parts[3];
if (backup_template_name != mServiceInfo.template_name) { // if (backup_template_name != mServiceInfo.template_name) {
std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl; // std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl;
return false; // return false;
} // }
std::string nicedate = std::string(backup_datetime).substr(0, 10); // std::string nicedate = std::string(backup_datetime).substr(0, 10);
std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<<backup_server_name<<", onto "<<mServer<<"/"<<mService<<std::endl; // std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<<backup_server_name<<", onto "<<mServer<<"/"<<mService<<std::endl;
std::cout << std::endl; // std::cout << std::endl;
std::cout << "*** ALL DATA FOR "<<mServer<<"/"<<mService<<" WILL BE OVERWRITTEN! ***"<<std::endl; // std::cout << "*** ALL DATA FOR "<<mServer<<"/"<<mService<<" WILL BE OVERWRITTEN! ***"<<std::endl;
// run the restore script // // run the restore script
std::cout << "OK, here goes..." << std::endl; // std::cout << "OK, here goes..." << std::endl;
{ // backup existing service // { // backup existing service
maketitle("1) Backing up old service... "); // maketitle("1) Backing up old service... ");
if (!backup(true)) // silent=true // if (!backup(true)) // silent=true
{ // {
std::cerr << std::endl; // std::cerr << std::endl;
std::cerr << "Error: Backup failed, restore aborted." << std::endl; // std::cerr << "Error: Backup failed, restore aborted." << std::endl;
std::cerr << "You can try using dropshell install "<<mServer<<" "<<mService<<" to install the service afresh." << std::endl; // std::cerr << "You can try using dropshell install "<<mServer<<" "<<mService<<" to install the service afresh." << std::endl;
std::cerr << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl; // std::cerr << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl;
return false; // return false;
} // }
std::cout << "Backup complete." << std::endl; // std::cout << "Backup complete." << std::endl;
} // }
{ // uninstall service, then nuke it. // { // uninstall service, then nuke it.
maketitle("2) Uninstalling old service..."); // maketitle("2) Uninstalling old service...");
// if (!uninstall(true)) // // if (!uninstall(true))
// return false; // // return false;
maketitle("3) Nuking old service..."); // maketitle("3) Nuking old service...");
// if (!nuke(true)) // // if (!nuke(true))
// return false; // // return false;
} // }
{ // restore service from backup // { // restore service from backup
maketitle("4) Restoring service data from backup..."); // maketitle("4) Restoring service data from backup...");
std::string remote_backups_dir = remotepath::backups(mServer); // std::string remote_backups_dir = remotepath::backups(mServer);
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file; // std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// Copy backup file from local to server // // Copy backup file from local to server
if (!scp_file_to_remote(local_backup_file_path, remote_backup_file_path, silent)) { // if (!scp_file_to_remote(local_backup_file_path, remote_backup_file_path, silent)) {
std::cerr << "Failed to copy backup file from local to server" << std::endl; // std::cerr << "Failed to copy backup file from local to server" << std::endl;
return false; // return false;
} // }
shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv); // shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
mServerEnv.run_remote_template_command(mService, "restore", {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}}); // mServerEnv.run_remote_template_command(mService, "restore", {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}});
} // dtor of remote_temp_folder will clean up the temp folder on the server // } // dtor of remote_temp_folder will clean up the temp folder on the server
// { // installing fresh service // // { // installing fresh service
// maketitle("5) Non-destructive install of fresh service..."); // // maketitle("5) Non-destructive install of fresh service...");
// if (!install_service(mServer, mService, true)) // // if (!install_service(mServer, mService, true))
// return false; // // return false;
// } // // }
bool healthy = false; // bool healthy = false;
{// healthcheck the service // {// healthcheck the service
maketitle("6) Healthchecking service..."); // maketitle("6) Healthchecking service...");
std::string green_tick = "\033[32m✓\033[0m"; // std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m"; // std::string red_cross = "\033[31m✗\033[0m";
healthy= (mServerEnv.run_remote_template_command(mService, "status", {}, silent, {})); // healthy= (mServerEnv.run_remote_template_command(mService, "status", {}, silent, {}));
if (!silent) // if (!silent)
std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl; // std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
} // }
return healthy; // return healthy;
} // }
// backup the service over ssh, using the credentials from server.env (via server_env.hpp) // // backup the service over ssh, using the credentials from server.env (via server_env.hpp)
// 1. run backup.sh on the server // // 1. run backup.sh on the server
// 2. create a backup file with format server-service-datetime.tgz // // 2. create a backup file with format server-service-datetime.tgz
// 3. store it in the server's DROPSHELL_DIR/backups folder // // 3. store it in the server's DROPSHELL_DIR/backups folder
// 4. copy it to the local user_dir/backups folder // // 4. copy it to the local user_dir/backups folder
// ------------------------------------------------------------------------------------------------ // // ------------------------------------------------------------------------------------------------
// Backup the service. // // Backup the service.
// ------------------------------------------------------------------------------------------------ // // ------------------------------------------------------------------------------------------------
bool service_runner::backup(bool silent) { // bool service_runner::backup(bool silent) {
auto service_info = get_service_info(mServer, mService); // auto service_info = get_service_info(mServer, mService);
if (service_info.local_service_path.empty()) { // if (service_info.local_service_path.empty()) {
std::cerr << "Error: Service not found" << std::endl; // std::cerr << "Error: Service not found" << std::endl;
return 1; // return 1;
} // }
const std::string command = "backup"; // const std::string command = "backup";
if (!gTemplateManager().template_command_exists(service_info.template_name, command)) { // if (!gTemplateManager().template_command_exists(service_info.template_name, command)) {
std::cout << "No backup script for " << service_info.template_name << std::endl; // std::cout << "No backup script for " << service_info.template_name << std::endl;
return true; // nothing to back up. // return true; // nothing to back up.
} // }
// Check if basic installed stuff is in place. // // Check if basic installed stuff is in place.
std::string remote_service_template_path = remotepath::service_template(mServer, mService); // std::string remote_service_template_path = remotepath::service_template(mServer, mService);
std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh"; // std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
std::string remote_service_config_path = remotepath::service_config(mServer, mService); // std::string remote_service_config_path = remotepath::service_config(mServer, mService);
if (!mServerEnv.check_remote_items_exist({ // if (!mServerEnv.check_remote_items_exist({
remotepath::service(mServer, mService), // remotepath::service(mServer, mService),
remote_command_script_file, // remote_command_script_file,
remotefile::service_env(mServer, mService)}) // remotefile::service_env(mServer, mService)})
) // )
{ // {
std::cerr << "Error: Required service directories not found on remote server" << std::endl; // std::cerr << "Error: Required service directories not found on remote server" << std::endl;
std::cerr << "Is the service installed?" << std::endl; // std::cerr << "Is the service installed?" << std::endl;
return false; // return false;
} // }
// Create backups directory on server if it doesn't exist // // Create backups directory on server if it doesn't exist
std::string remote_backups_dir = remotepath::backups(mServer); // std::string remote_backups_dir = remotepath::backups(mServer);
if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl; // if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir); // std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Silent)) { // if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Silent)) {
std::cerr << "Failed to create backups directory on server" << std::endl; // std::cerr << "Failed to create backups directory on server" << std::endl;
return false; // return false;
} // }
// Create backups directory locally if it doesn't exist // // Create backups directory locally if it doesn't exist
std::string local_backups_dir = gConfig().get_local_backup_path(); // std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty()) { // if (local_backups_dir.empty()) {
std::cerr << "Error: Local backups directory not found" << std::endl; // std::cerr << "Error: Local backups directory not found" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl; // std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false; // return false;
} // }
if (!std::filesystem::exists(local_backups_dir)) // if (!std::filesystem::exists(local_backups_dir))
std::filesystem::create_directories(local_backups_dir); // std::filesystem::create_directories(local_backups_dir);
// Get current datetime for backup filename // // Get current datetime for backup filename
auto now = std::chrono::system_clock::now(); // auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now); // auto time = std::chrono::system_clock::to_time_t(now);
std::stringstream datetime; // std::stringstream datetime;
datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S"); // datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
// Construct backup filename // // Construct backup filename
shared_commands::cBackupFileName backup_filename_construction(mServer, mService, service_info.template_name); // shared_commands::cBackupFileName backup_filename_construction(mServer, mService, service_info.template_name);
if (!backup_filename_construction.is_valid()) { // if (!backup_filename_construction.is_valid()) {
std::cerr << "Invalid backup filename" << std::endl; // std::cerr << "Invalid backup filename" << std::endl;
return false; // return false;
} // }
std::string backup_filename = backup_filename_construction.get_filename(); // std::string backup_filename = backup_filename_construction.get_filename();
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_filename; // std::string remote_backup_file_path = remote_backups_dir + "/" + backup_filename;
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string(); // std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string();
// assert that the backup filename is valid - -_- appears exactly 3 times in local_backup_file_path. // // assert that the backup filename is valid - -_- appears exactly 3 times in local_backup_file_path.
ASSERT(3 == count_substring("-_-", local_backup_file_path), "Invalid backup filename"); // ASSERT(3 == count_substring("-_-", local_backup_file_path), "Invalid backup filename");
{ // Run backup script // { // Run backup script
shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv); // shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
if (!mServerEnv.run_remote_template_command(mService, command, {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) { // if (!mServerEnv.run_remote_template_command(mService, command, {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) {
std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; // std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl;
return false; // return false;
} // }
// Copy backup file from server to local // // Copy backup file from server to local
if (!scp_file_from_remote(remote_backup_file_path, local_backup_file_path, silent)) { // if (!scp_file_from_remote(remote_backup_file_path, local_backup_file_path, silent)) {
std::cerr << "Failed to copy backup file from server" << std::endl; // std::cerr << "Failed to copy backup file from server" << std::endl;
return false; // return false;
} // }
} // dtor of remote_temp_folder will clean up the temp folder on the server // } // dtor of remote_temp_folder will clean up the temp folder on the server
if (!silent) { // if (!silent) {
std::cout << "Backup created successfully. Restore with:"<<std::endl; // std::cout << "Backup created successfully. Restore with:"<<std::endl;
std::cout << " dropshell restore " << mServer << " " << mService << " " << backup_filename << std::endl; // std::cout << " dropshell restore " << mServer << " " << mService << " " << backup_filename << std::endl;
} // }
return true; // return true;
} // }
// Helper function to get the latest backup file for a given server and service // // Helper function to get the latest backup file for a given server and service
std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) { // std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) {
std::string local_backups_dir = gConfig().get_local_backup_path(); // std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) { // if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl; // std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return ""; // return "";
} // }
// Get the template name for this service // // Get the template name for this service
LocalServiceInfo info = get_service_info(server, service); // LocalServiceInfo info = get_service_info(server, service);
if (info.template_name.empty()) { // if (info.template_name.empty()) {
std::cerr << "Error: Could not determine template name for service: " << service << std::endl; // std::cerr << "Error: Could not determine template name for service: " << service << std::endl;
return ""; // return "";
} // }
// Build the expected prefix for backup files // // Build the expected prefix for backup files
std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-"; // std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-";
std::string latest_file; // std::string latest_file;
std::string latest_datetime; // std::string latest_datetime;
std::cout << "Looking for backup files in " << local_backups_dir << std::endl; // std::cout << "Looking for backup files in " << local_backups_dir << std::endl;
for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) { // for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
if (!entry.is_regular_file()) continue; // if (!entry.is_regular_file()) continue;
std::string filename = entry.path().filename().string(); // std::string filename = entry.path().filename().string();
if (filename.rfind(prefix, 0) == 0) { // starts with prefix // if (filename.rfind(prefix, 0) == 0) { // starts with prefix
// Extract the datetime part // // Extract the datetime part
size_t dt_start = prefix.size(); // size_t dt_start = prefix.size();
size_t dt_end = filename.find(".tgz", dt_start); // size_t dt_end = filename.find(".tgz", dt_start);
if (dt_end == std::string::npos) continue; // if (dt_end == std::string::npos) continue;
std::string datetime = filename.substr(dt_start, dt_end - dt_start); // std::string datetime = filename.substr(dt_start, dt_end - dt_start);
std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl; // std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl;
if (datetime > latest_datetime) { // if (datetime > latest_datetime) {
latest_datetime = datetime; // latest_datetime = datetime;
latest_file = filename; // latest_file = filename;
} // }
} // }
} // }
if (latest_file.empty()) { // if (latest_file.empty()) {
std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl; // std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl;
} // }
std::cout << "Latest backup file: " << latest_file << std::endl; // std::cout << "Latest backup file: " << latest_file << std::endl;
return latest_file; // return latest_file;
} // }
} // namespace dropshell // } // namespace dropshell

View File

@ -4,8 +4,6 @@
#include "templates.hpp" #include "templates.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "server_env_manager.hpp"
#include "servers.hpp"
#include "assert.hpp" #include "assert.hpp"
#include <iostream> #include <iostream>
@ -13,211 +11,265 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace dropshell { 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();
}
std::vector<LocalServiceInfo> get_server_services_info(const std::string& server_name) {
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()) {
std::cerr << "Error: No local server definition paths found" << std::endl;
std::cerr << "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);
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;
}
LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name)
{ {
LocalServiceInfo service;
if (server_name.empty() || service_name.empty()) #pragma message("TODO : Smart test that the service is fully valid.")
return LocalServiceInfo(); bool SIvalid(const LocalServiceInfo &service_info)
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))
{ {
std::cerr << "Error: Service directory not found: " << service.local_service_path << std::endl; return !service_info.service_name.empty() &&
return LocalServiceInfo(); !service_info.template_name.empty() &&
!service_info.local_service_path.empty() &&
!service_info.local_template_path.empty() &&
!service_info.user.empty();
} }
// now set the template name and path. std::vector<LocalServiceInfo> get_server_services_info(const std::string &server_name)
std::map<std::string, std::string> variables; {
if (!get_all_service_env_vars(server_name, service_name, variables)) std::vector<LocalServiceInfo> services;
return LocalServiceInfo();
// confirm TEMPLATE is defined.
auto it = variables.find("TEMPLATE");
if (it == variables.end()) {
std::cerr << "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); if (server_name.empty())
if (!tinfo.is_set()) { return services;
std::cerr << "Error: Template '" << service.template_name << "' not found" << std::endl;
return LocalServiceInfo();
}
// find the template path std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
service.local_template_path = tinfo.local_template_path(); if (local_server_definition_paths.empty())
{
std::cerr << "Error: No local server definition paths found" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return services;
}
return service; for (const auto &server_definition_path : local_server_definition_paths)
} {
fs::path serverpath = server_definition_path + "/" + server_name;
std::set<std::string> get_used_commands(const std::string &server_name, const std::string &service_name) if (fs::exists(serverpath)) // service is on that server...
{ for (const auto &entry : fs::directory_iterator(serverpath))
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()) {
std::cerr << "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()) {
std::cerr << "Error: Service not found: " << service_name << std::endl;
return backups;
}
std::string backups_dir = gConfig().get_local_backup_path();
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()); 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);
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;
} }
return backups;
}
bool get_bool_variable(const std::map<std::string, std::string> &variables, const std::string &variable_name)
bool get_all_service_env_vars(const std::string &server_name, const std::string &service_name, std::map<std::string, std::string> & all_env_vars)
{
all_env_vars.clear();
if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name)))
{ {
std::cerr << "Error: Service not found: " << service_name << " on server " << server_name << std::endl; auto it = variables.find(variable_name);
return false; if (it == variables.end())
} {
error << "Variable " << variable_name << " not found in the service " << filenames::template_info_env << std::endl;
ServerInfo server_info = get_server_info(server_name); return false;
if (server_info.ssh_host.empty())
std::cerr << "Error: Server " << server_name << " not found - ssh_host empty, so HOST_NAME not set" << std::endl;
// add in some handy variables.
// if we change these, we also need to update agent/_allservicesstatus.sh
all_env_vars["CONFIG_PATH"] = remotepath::service_config(server_name,service_name);
all_env_vars["SERVER"] = server_name;
all_env_vars["SERVICE"] = service_name;
all_env_vars["AGENT_PATH"] = remotepath::agent(server_name);
all_env_vars["HOST_NAME"] = server_info.ssh_host;
all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.
// 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)) {
std::map<std::string, std::string> env_vars;
envmanager env_manager(file);
env_manager.load();
env_manager.get_all_variables(env_vars);
all_env_vars.merge(env_vars);
} }
else return it->second == "true";
warning << "Expected environment file not found: " << file << std::endl;
};
// Load environment files
load_env_file(localfile::service_env(server_name, service_name));
load_env_file(localfile::template_info_env(server_name, service_name));
// determine template name.
auto it = all_env_vars.find("TEMPLATE");
if (it == all_env_vars.end()) {
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
info << "The TEMPLATE variable is required to determine the template name." << std::endl;
info << "Please check the service.env file and the .template_info.env file in:" << std::endl;
info << " " << localpath::service(server_name, service_name) << std::endl << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(it->second);
if (!tinfo.is_set()) {
std::cerr << "Error: Template '" << it->second << "' not found" << std::endl;
return false;
} }
std::string default_env_file = tinfo.local_template_path()/"_default.env"; LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name)
if (!fs::exists(default_env_file)) { {
std::cerr << "Error: Template default env file '" << default_env_file << "' not found" << std::endl; LocalServiceInfo service;
return false;
if (server_name.empty() || service_name.empty())
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))
{
std::cerr << "Error: Service directory not found: " << service.local_service_path << std::endl;
return LocalServiceInfo();
}
// now set the template name and path.
std::map<std::string, std::string> variables;
if (!get_all_service_env_vars(server_name, service_name, variables))
return LocalServiceInfo();
{ // confirm TEMPLATE is defined.
auto it = variables.find("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);
if (!tinfo.is_set())
{
error << "Template specified '" << service.template_name << "' could not be found" << std::endl;
return LocalServiceInfo();
}
// find the template path
service.local_template_path = tinfo.local_template_path();
{ // set the user.
auto it = variables.find("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");
{ // determine if the service template hash matches the template hash.
auto it = variables.find("TEMPLATE_HASH");
if (it == variables.end())
error << "Variable TEMPLATE_HASH not found in the service " << filenames::template_info_env << std::endl;
else
{
uint64_t service_template_hash = std::stoull(it->second);
service.service_template_hash_match = (service_template_hash == tinfo.hash());
//debug << "Service template hash: " << service_template_hash << " == " << tinfo.hash() << std::endl;
}
}
return service;
} }
load_env_file(default_env_file); std::set<std::string> get_used_commands(const std::string &server_name, const std::string &service_name)
return true; {
} 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())
{
std::cerr << "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())
{
std::cerr << "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, std::map<std::string, std::string> &all_env_vars)
{
all_env_vars.clear();
if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name)))
{
std::cerr << "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))
{
std::map<std::string, std::string> env_vars;
envmanager env_manager(file);
env_manager.load();
env_manager.get_all_variables(env_vars);
all_env_vars.merge(env_vars);
}
else
warning << "Expected environment file not found: " << file << std::endl;
};
// Load environment files
load_env_file(localfile::service_env(server_name, service_name));
load_env_file(localfile::template_info_env(server_name, service_name));
std::string user = 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;
}
// add in some handy variables.
// if we change these, we also need to update agent/_allservicesstatus.sh
all_env_vars["CONFIG_PATH"] = remotepath(server_name, user).service_config(service_name);
all_env_vars["SERVER"] = server_name;
all_env_vars["SERVICE"] = service_name;
all_env_vars["AGENT_PATH"] = remotepath(server_name, user).agent();
all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.
// determine template name.
auto it = all_env_vars.find("TEMPLATE");
if (it == all_env_vars.end())
{
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
info << "The TEMPLATE variable is required to determine the template name." << std::endl;
info << "Please check the " << filenames::service_env << " file and the "<< filenames::template_info_env << " file in:" << std::endl;
info << " " << localpath::service(server_name, service_name) << std::endl
<< std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(it->second);
if (!tinfo.is_set())
{
std::cerr << "Error: Template '" << it->second << "' not found" << std::endl;
return false;
}
return true;
}
} // namespace dropshell } // namespace dropshell

View File

@ -13,6 +13,11 @@ namespace dropshell {
std::string template_name; std::string template_name;
std::string local_service_path; std::string local_service_path;
std::string local_template_path; std::string local_template_path;
std::string user;
bool requires_host_root;
bool requires_docker;
bool requires_docker_root;
bool service_template_hash_match;
}; };
bool SIvalid(const LocalServiceInfo& service_info); bool SIvalid(const LocalServiceInfo& service_info);
@ -27,6 +32,7 @@ namespace dropshell {
// list all backups for a given service (across all servers) // list all backups for a given service (across all servers)
std::set<std::string> list_backups(const std::string& server_name, const std::string& service_name); std::set<std::string> list_backups(const std::string& server_name, const std::string& service_name);
} // namespace dropshell } // namespace dropshell
#endif #endif

View File

@ -13,6 +13,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/hash.hpp"
namespace dropshell { namespace dropshell {
@ -185,7 +186,7 @@
return false; return false;
} }
auto local_template_paths = gConfig().get_template_local_paths(); auto local_template_paths = gConfig().get_local_template_paths();
if (local_template_paths.empty()) { if (local_template_paths.empty()) {
std::cerr << "Error: No local template paths found" << std::endl; std::cerr << "Error: No local template paths found" << std::endl;
std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl; std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl;
@ -219,9 +220,9 @@
// modify the TEMPLATE=example line in the .template_info.env file to TEMPLATE=<template_name> // modify the TEMPLATE=example line in the .template_info.env file to TEMPLATE=<template_name>
std::string search_string = "TEMPLATE="; std::string search_string = "TEMPLATE=";
std::string replacement_line = "TEMPLATE=" + template_name; std::string replacement_line = "TEMPLATE=" + template_name;
std::string service_env_path = new_template_path + "/config/.template_info.env"; std::string service_env_path = new_template_path + "/config/" + filenames::template_info_env;
if (!replace_line_in_file(service_env_path, search_string, replacement_line)) { if (!replace_line_in_file(service_env_path, search_string, replacement_line)) {
std::cerr << "Error: Failed to replace TEMPLATE= line in the .template_info.env file" << std::endl; std::cerr << "Error: Failed to replace TEMPLATE= line in the " << filenames::template_info_env <<" file" << std::endl;
return false; return false;
} }
@ -252,7 +253,7 @@
ASSERT(mSources.empty(), "Template manager already loaded (sources are not empty)."); ASSERT(mSources.empty(), "Template manager already loaded (sources are not empty).");
ASSERT(gConfig().is_config_set(), "Config not set."); ASSERT(gConfig().is_config_set(), "Config not set.");
ASSERT(!mLoaded, "Template manager already loaded."); ASSERT(!mLoaded, "Template manager already loaded.");
auto local_template_paths = gConfig().get_template_local_paths(); auto local_template_paths = gConfig().get_local_template_paths();
if (local_template_paths.empty()) if (local_template_paths.empty())
return; return;
for (const auto& path : local_template_paths) for (const auto& path : local_template_paths)
@ -305,9 +306,8 @@
std::string template_name = std::filesystem::path(template_path).filename().string(); std::string template_name = std::filesystem::path(template_path).filename().string();
std::vector<std::string> required_files = { std::vector<std::string> required_files = {
"config/service.env", "config/" + filenames::service_env,
"config/.template_info.env", "config/" + filenames::template_info_env,
"_default.env",
"install.sh", "install.sh",
"uninstall.sh" "uninstall.sh"
}; };
@ -331,8 +331,8 @@
// check TEMPLATE= line. // check TEMPLATE= line.
std::map<std::string, std::string> all_env_vars; std::map<std::string, std::string> all_env_vars;
std::vector<std::string> env_files = { std::vector<std::string> env_files = {
"config/service.env", "config/" + filenames::service_env,
"config/.template_info.env" "config/" + filenames::template_info_env
}; };
for (const auto& file : env_files) { for (const auto& file : env_files) {
{ // load service.env from the service on this machine. { // load service.env from the service on this machine.
@ -376,8 +376,16 @@
mLocationID(location_id), mLocationID(location_id),
mTemplateLocalPath(local_template_path), mTemplateLocalPath(local_template_path),
mTemplateValid(template_manager::test_template(local_template_path.string())), mTemplateValid(template_manager::test_template(local_template_path.string())),
mIsSet(!template_name.empty() && !location_id.empty() && !local_template_path.empty()) mIsSet(!template_name.empty() && !location_id.empty() && !local_template_path.empty()),\
mHash(0)
{ {
if (!std::filesystem::exists(local_template_path))
{
error << "Template path does not exist: " << local_template_path << std::endl;
return;
}
mHash = hash_directory_recursive(local_template_path);
} }
} // namespace dropshell } // namespace dropshell

View File

@ -4,7 +4,9 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include "utils/json.hpp" #define JSON_INLINE_ALL
#include "json.hpp"
namespace dropshell { namespace dropshell {
@ -24,12 +26,15 @@ class template_info {
std::string locationID() const { return mLocationID; } std::string locationID() const { return mLocationID; }
std::filesystem::path local_template_path() const { return mTemplateLocalPath; } std::filesystem::path local_template_path() const { return mTemplateLocalPath; }
bool template_valid() const { return mTemplateValid; } bool template_valid() const { return mTemplateValid; }
uint64_t hash() const { return mHash; }
private: private:
std::string mTemplateName; std::string mTemplateName;
std::string mLocationID; std::string mLocationID;
std::filesystem::path mTemplateLocalPath; // source or cache. std::filesystem::path mTemplateLocalPath; // source or cache.
bool mTemplateValid; bool mTemplateValid;
bool mIsSet; bool mIsSet;
uint64_t mHash;
}; };
class template_source_interface { class template_source_interface {

View File

@ -9,4 +9,5 @@
std::exit(1); \ std::exit(1); \
} }
#endif // ASSERT_HPP #endif // ASSERT_HPP

View File

@ -1,6 +1,6 @@
#include "directories.hpp" #include "directories.hpp"
#include "config.hpp" #include "config.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "output.hpp" #include "output.hpp"
#include <iostream> #include <iostream>
@ -19,7 +19,7 @@ namespace localfile {
// Try ~/.config/dropshell/dropshell.json // Try ~/.config/dropshell/dropshell.json
std::string homedir = localpath::current_user_home(); std::string homedir = localpath::current_user_home();
if (!homedir.empty()) { if (!homedir.empty()) {
fs::path user_path = fs::path(homedir) / ".config" / "dropshell" / "dropshell.json"; fs::path user_path = fs::path(homedir) / ".config" / "dropshell" / filenames::dropshell_json;
return user_path.string(); return user_path.string();
} }
return std::string(); return std::string();
@ -27,18 +27,28 @@ namespace localfile {
std::string server_json(const std::string &server_name) { std::string server_json(const std::string &server_name) {
std::string serverpath = localpath::server(server_name); std::string serverpath = localpath::server(server_name);
return (serverpath.empty() ? "" : (fs::path(serverpath) / "server.json").string()); return (serverpath.empty() ? "" : (fs::path(serverpath) / filenames::server_json).string());
} }
std::string service_env(const std::string &server_name, const std::string &service_name) { std::string service_env(const std::string &server_name, const std::string &service_name) {
std::string servicepath = localpath::service(server_name, service_name); std::string servicepath = localpath::service(server_name, service_name);
return (servicepath.empty() ? "" : (fs::path(servicepath) / "service.env").string()); return (servicepath.empty() ? "" : (fs::path(servicepath) / filenames::service_env).string());
} }
std::string template_info_env(const std::string &server_name, const std::string &service_name) std::string template_info_env(const std::string &server_name, const std::string &service_name)
{ {
std::string servicepath = localpath::service(server_name, service_name); std::string servicepath = localpath::service(server_name, service_name);
return (servicepath.empty() ? "" : (fs::path(servicepath) / ".template_info.env").string()); return (servicepath.empty() ? "" : (fs::path(servicepath) / filenames::template_info_env).string());
}
std::string template_example()
{
return localpath::agent_local() + "/template_example";
}
std::string bb64()
{
return localpath::agent_local() + "/bb64";
} }
} // namespace localfile } // namespace localfile
@ -62,16 +72,17 @@ namespace localpath {
std::string remote_versions(const std::string &server_name, const std::string &service_name) std::string remote_versions(const std::string &server_name, const std::string &service_name)
{ {
std::string template_cache_path = gConfig().get_local_template_cache_path(); std::string template_cache_path = localpath::template_cache();
return ((template_cache_path.empty() || service_name.empty()) ? "" : return ((template_cache_path.empty() || service_name.empty()) ? "" :
(template_cache_path+"/remote_versions/"+service_name+".json")); (template_cache_path+"/remote_versions/"+service_name+".json"));
} }
std::string agent(){ std::string agent_local()
return current_user_home() + "/.local/dropshell_agent";
}
std::string files_for_remote_agent()
{ {
return agent() + "/files_for_remote_agent"; return current_user_home()+"/.local/dropshell_agent/agent-local";
}
std::string agent_remote()
{
return current_user_home() + "/.local/dropshell_agent/agent-remote";
} }
std::string current_user_home() std::string current_user_home()
{ {
@ -84,6 +95,50 @@ namespace localpath {
warning << "Couldn't determine user directory" << std::endl; warning << "Couldn't determine user directory" << std::endl;
return std::string(); return std::string();
} }
std::string dropshell_files()
{
return current_user_home() + "/.local/dropshell_files";
return std::string();
}
std::string backups()
{
return dropshell_files() + "/backups";
}
std::string temp_files()
{
return dropshell_files() + "/temp_files";
}
std::string template_cache()
{
return dropshell_files() + "template_cache";
}
bool create_directories()
{
std::vector<std::filesystem::path> paths = {
dropshell_files(),
agent_local(),
agent_remote(),
template_cache(),
backups(),
temp_files()
};
for (auto &p : gConfig().get_local_server_definition_paths())
paths.push_back(p);
for (auto &p : paths)
if (!p.empty() && !std::filesystem::exists(p))
{
info << "Creating directory: " << p << std::endl;
std::filesystem::create_directories(p);
}
return false;
}
} // namespace localpath } // namespace localpath
//------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------
@ -99,78 +154,70 @@ namespace localpath {
// |-- config // |-- config
// |-- service.env (actual service config) // |-- service.env (actual service config)
// |-- template // |-- template
// |-- _default.env
// |-- (script files) // |-- (script files)
// |-- config // |-- config
// |-- service.env (default service config) // |-- service.env (default service config)
// |-- (other config files for specific server&service) // |-- (other config files for specific server&service)
remotefile::remotefile(const std::string &server_name, const std::string &user) :
mServer_name(server_name), mUser(user) {}
namespace remotefile { std::string remotefile::service_env(const std::string &service_name) const
std::string service_env(const std::string &server_name, const std::string &service_name)
{ {
return remotepath::service_config(server_name, service_name) + "/service.env"; return remotepath(mServer_name,mUser).service_config(service_name) + "/" + filenames::service_env;
} }
}
namespace remotepath { remotepath::remotepath(const std::string &server_name, const std::string &user) : mServer_name(server_name), mUser(user) {}
std::string DROPSHELL_DIR(const std::string &server_name)
std::string remotepath::DROPSHELL_DIR() const
{ {
return server_env_manager(server_name).get_DROPSHELL_DIR(); return ServerConfig(mServer_name).get_user_dir(mUser);
} }
std::string services(const std::string &server_name) std::string remotepath::services() const
{ {
std::string dsp = DROPSHELL_DIR(server_name); std::string dsp = DROPSHELL_DIR();
return (dsp.empty() ? "" : (dsp + "/services")); return (dsp.empty() ? "" : (dsp + "/services"));
} }
std::string service(const std::string &server_name, const std::string &service_name) std::string remotepath::service(const std::string &service_name) const
{ {
std::string services_path = services(server_name); std::string services_path = services();
return (services_path.empty() ? "" : (services_path + "/" + service_name)); return (services_path.empty() ? "" : (services_path + "/" + service_name));
} }
std::string service_config(const std::string &server_name, const std::string &service_name) std::string remotepath::service_config(const std::string &service_name) const
{ {
std::string service_path = service(server_name, service_name); std::string service_path = service(service_name);
return (service_path.empty() ? "" : (service_path + "/config")); return (service_path.empty() ? "" : (service_path + "/config"));
} }
std::string service_template(const std::string &server_name, const std::string &service_name) std::string remotepath::service_template(const std::string &service_name) const
{ {
std::string service_path = service(server_name, service_name); std::string service_path = service(service_name);
return (service_path.empty() ? "" : (service_path + "/template")); return (service_path.empty() ? "" : (service_path + "/template"));
} }
std::string backups(const std::string &server_name) std::string remotepath::backups() const
{ {
std::string dsp = DROPSHELL_DIR(server_name); std::string dsp = DROPSHELL_DIR();
return (dsp.empty() ? "" : (dsp + "/backups")); return (dsp.empty() ? "" : (dsp + "/backups"));
} }
std::string temp_files(const std::string &server_name) std::string remotepath::temp_files() const
{ {
std::string dsp = DROPSHELL_DIR(server_name); std::string dsp = DROPSHELL_DIR();
return (dsp.empty() ? "" : (dsp + "/temp_files")); return (dsp.empty() ? "" : (dsp + "/temp_files"));
} }
std::string agent(const std::string &server_name) std::string remotepath::agent() const
{ {
std::string dsp = DROPSHELL_DIR(server_name); std::string dsp = DROPSHELL_DIR();
return (dsp.empty() ? "" : (dsp + "/agent")); return (dsp.empty() ? "" : (dsp + "/agent"));
} }
std::string service_env(const std::string &server_name, const std::string &service_name)
{
std::string service_path = service_config(server_name, service_name);
return (service_path.empty() ? "" : (service_path + "/service.env"));
}
} // namespace remotepath
// ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------
// Utility functions // Utility functions

View File

@ -14,10 +14,27 @@ namespace dropshell {
// ~/.config/dropshell/dropshell.json // ~/.config/dropshell/dropshell.json
// ~/.local/dropshell_agent // ~/.local/dropshell_agent
// |-- bb64 (only used locally, as it's for the local machine's architecture!) // |-- agent-local
// |-- files_for_remote_agent // |-- agent-install.sh
// |-- (other agent files, including _allservicesstatus.sh) // |-- bb64 (only used locally, as it's for the local machine's architecture!)
// |-- template_example
// |-- agent-remote
// |-- (remote agent files, including _allservicesstatus.sh)
// ~/.local/dropshell_files
// |-- backups
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// |-- temp_files
// |-- template_cache
// |-- templates
// | |-- <template_name>.json
// | |-- <template_name>
// | |-- (...script files...)
// | |-- config
// | |-- service.env
// | |-- .template_info.env
// | |-- (...other service config files...)
// server_definition_path // server_definition_path
// |-- <server_name> // |-- <server_name>
// |-- server.json // |-- server.json
@ -27,23 +44,13 @@ namespace dropshell {
// |-- .template_info.env // |-- .template_info.env
// |-- (...other config files for specific server&service...) // |-- (...other config files for specific server&service...)
// backup path namespace filenames {
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz static const std::string template_info_env = ".template_info.env";
static const std::string service_env = "service.env";
// temp files path static const std::string readme = "README.txt";
static const std::string server_json = "server.json";
// template cache path static const std::string dropshell_json = "dropshell.json";
// |-- templates } // namespace filenames.
// | |-- <template_name>.json
// | |-- <template_name>
// | |-- (...script files...)
// | |-- _default.env
// | |-- config
// | |-- service.env
// | |-- .template_info.env
// | |-- (...other service config files...)
// |-- remote_versions
// | |-- server_name-service_name.json
namespace localfile { namespace localfile {
// ~/.config/dropshell/dropshell.json // ~/.config/dropshell/dropshell.json
@ -51,6 +58,8 @@ namespace dropshell {
std::string server_json(const std::string &server_name); std::string server_json(const std::string &server_name);
std::string service_env(const std::string &server_name, const std::string &service_name); std::string service_env(const std::string &server_name, const std::string &service_name);
std::string template_info_env(const std::string &server_name, const std::string &service_name); std::string template_info_env(const std::string &server_name, const std::string &service_name);
std::string template_example();
std::string bb64();
} // namespace localfile } // namespace localfile
namespace localpath { namespace localpath {
@ -59,9 +68,16 @@ namespace dropshell {
std::string remote_versions(const std::string &server_name, const std::string &service_name); std::string remote_versions(const std::string &server_name, const std::string &service_name);
std::string agent(); std::string agent_local();
std::string files_for_remote_agent(); std::string agent_remote();
std::string current_user_home(); std::string current_user_home();
std::string dropshell_files();
std::string backups();
std::string temp_files();
std::string template_cache();
bool create_directories();
} // namespace local } // namespace local
@ -79,27 +95,36 @@ namespace dropshell {
// |-- service.env (actual service config) // |-- service.env (actual service config)
// |-- .template_info.env // |-- .template_info.env
// |-- template // |-- template
// |-- _default.env
// |-- (script files) // |-- (script files)
// |-- config // |-- config
// |-- service.env (default service config) // |-- service.env (default service config)
// |-- .template_info.env // |-- .template_info.env
// |-- (other config files for specific server&service) // |-- (other config files for specific server&service)
namespace remotefile { class remotefile {
std::string service_env(const std::string &server_name, const std::string &service_name); public:
} // namespace remotefile remotefile(const std::string &server_name, const std::string &user);
std::string service_env(const std::string &service_name) const;
private:
std::string mServer_name;
std::string mUser;
};
namespace remotepath { class remotepath {
std::string DROPSHELL_DIR(const std::string &server_name); public:
std::string services(const std::string &server_name); remotepath(const std::string &server_name, const std::string &user);
std::string service(const std::string &server_name, const std::string &service_name); std::string DROPSHELL_DIR() const;
std::string service_config(const std::string &server_name, const std::string &service_name); std::string services() const;
std::string service_template(const std::string &server_name, const std::string &service_name); std::string service(const std::string &service_name) const;
std::string backups(const std::string &server_name); std::string service_config(const std::string &service_name) const;
std::string temp_files(const std::string &server_name); std::string service_template(const std::string &service_name) const;
std::string agent(const std::string &server_name); std::string backups() const;
} // namespace remotepath std::string temp_files() const;
std::string agent() const;
private:
std::string mServer_name;
std::string mUser;
};
//------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------
// utility functions // utility functions

View File

@ -29,7 +29,7 @@ namespace dropshell
{ {
if (command.get_command_to_run().empty()) if (command.get_command_to_run().empty())
return false; return false;
std::string full_command = command.construct_cmd(localpath::agent()+"/bb64"); // Get the command string std::string full_command = command.construct_cmd(localfile::bb64()); // Get the command string
pid_t pid = fork(); pid_t pid = fork();
@ -130,7 +130,7 @@ namespace dropshell
std::string full_cmd; std::string full_cmd;
if (!hasFlag(mode, cMode::NoBB64)) if (!hasFlag(mode, cMode::NoBB64))
full_cmd = command.construct_cmd(localpath::agent()+"/bb64"); full_cmd = command.construct_cmd(localfile::bb64());
else else
full_cmd = command.construct_cmd(""); full_cmd = command.construct_cmd("");
@ -165,13 +165,13 @@ namespace dropshell
return false; return false;
std::stringstream ssh_cmd; std::stringstream ssh_cmd;
ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "") ssh_cmd << "ssh -p " << ssh_info.get_port() << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "")
<< ssh_info.user << "@" << ssh_info.host; << ssh_info.get_user() << "@" << ssh_info.get_host();
std::string remote_bb64_path; std::string remote_bb64_path;
if (!hasFlag(mode, cMode::NoBB64)) if (!hasFlag(mode, cMode::NoBB64))
remote_bb64_path = remotepath::agent(ssh_info.server_ID) + "/bb64"; remote_bb64_path = remotepath(ssh_info.get_server_ID(), ssh_info.get_user()).agent() + "/bb64";
bool rval = execute_local_command( bool rval = execute_local_command(
"", // local directory to run in "", // local directory to run in
@ -237,4 +237,15 @@ namespace dropshell
return cmdstr; return cmdstr;
} }
bool sSSHInfo::valid() const
{
if (host.empty() || user.empty() || port.empty() || server_ID.empty() || user_dir.empty())
return false;
if (atoi(port.c_str()) == 0)
return false;
return true;
}
} // namespace dropshell } // namespace dropshell

View File

@ -24,12 +24,25 @@ inline cMode operator|=(cMode & lhs, cMode rhs) {return lhs = lhs | rhs;}
inline bool hasFlag(cMode mode, cMode flag) {return (mode & flag) == flag;} inline bool hasFlag(cMode mode, cMode flag) {return (mode & flag) == flag;}
typedef struct sSSHInfo { class sSSHInfo {
std::string host; public:
std::string user; sSSHInfo(std::string host, std::string user, std::string port, std::string server_ID, std::string user_dir) :
std::string port; host(host), user(user), port(port), server_ID(server_ID), user_dir(user_dir) {}
std::string server_ID; // dropshell name for server.
} sSSHInfo; std::string get_host() const { return host; }
std::string get_user() const { return user; }
std::string get_port() const { return port; }
std::string get_server_ID() const { return server_ID; }
std::string get_user_dir() const { return user_dir; }
bool valid() const;
private:
std::string host;
std::string user;
std::string port;
std::string server_ID; // dropshell name for server.
std::string user_dir; // dropshell directory for the user.
};
bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars, std::string * output = nullptr, cMode mode = cMode::Defaults); bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars, std::string * output = nullptr, cMode mode = cMode::Defaults);
bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_command, cMode mode = cMode::Defaults, std::string * output = nullptr); bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_command, cMode mode = cMode::Defaults, std::string * output = nullptr);

View File

@ -147,6 +147,27 @@ void tableprint::set_title(const std::string title) {
this->title = title; this->title = title;
} }
// gives the columns to sort by, starting at 0.
void tableprint::sort(std::vector<int> sort_columns)
{
// Skip header row and sort remaining rows
if (rows.size() <= 1) return; // Only header or empty table
// Create a custom comparator that compares rows based on the specified columns
auto comparator = [this, &sort_columns](const std::vector<std::string>& a, const std::vector<std::string>& b) {
for (int col : sort_columns) {
if (col >= 0 && col < a.size() && col < b.size()) {
int cmp = a[col].compare(b[col]);
if (cmp != 0) return cmp < 0;
}
}
return false; // Equal rows maintain original order
};
// Sort rows starting from index 1 (after header)
std::sort(rows.begin() + 1, rows.end(), comparator);
}
void tableprint::add_row(const std::vector<std::string>& row) { void tableprint::add_row(const std::vector<std::string>& row) {
std::vector<std::string> trimmed_row; std::vector<std::string> trimmed_row;
for (const auto& cell : row) { for (const auto& cell : row) {

View File

@ -16,6 +16,7 @@ class tableprint {
void add_row(const std::vector<std::string>& row); void add_row(const std::vector<std::string>& row);
void print(); void print();
void set_title(const std::string title); void set_title(const std::string title);
void sort(std::vector<int> sort_columns);
private: private:
std::vector<std::vector<std::string>> rows; std::vector<std::vector<std::string>> rows;
std::string title; std::string title;

View File

@ -9,6 +9,7 @@
#include <random> #include <random>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#include <cctype>
namespace dropshell { namespace dropshell {
@ -432,4 +433,16 @@ std::string get_line_wrap(std::string &src, int maxchars)
return remove_return(out) + '\n'; return remove_return(out) + '\n';
} }
std::string tolower(const std::string& str) {
if (str.empty()) return str;
std::string result;
result.reserve(str.size()); // Pre-allocate space for efficiency
for (unsigned char c : str) {
result.push_back(std::tolower(c));
}
return result;
}
} // namespace dropshell } // namespace dropshell

View File

@ -59,4 +59,6 @@ int get_console_width();
std::string get_line_wrap(std::string & src, int maxchars); std::string get_line_wrap(std::string & src, int maxchars);
std::string tolower(const std::string& str);
} // namespace dropshell } // namespace dropshell