Compare commits

...

16 Commits

Author SHA1 Message Date
ddc57173cb .
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 11s
2025-05-24 19:27:48 +12:00
343e597d84 Working on nuke
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-24 17:16:19 +12:00
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
7a710b525f dropshell release 2025.0521.1906
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 19:06:50 +12:00
47 changed files with 1962 additions and 1777 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

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.10)
project(dropshell VERSION 1.0.0 LANGUAGES CXX) project(dropshell VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Set default build type to Release if not specified # Set default build type to Release if not specified
@ -80,8 +81,26 @@ if(WIN32)
) )
endif() endif()
# Configure libassert
include(FetchContent)
FetchContent_Declare(
libassert
GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git
GIT_TAG v2.1.5
)
FetchContent_MakeAvailable(libassert)
include(FetchContent)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.8.3
)
FetchContent_MakeAvailable(cpptrace)
# Link libraries # Link libraries
target_link_libraries(dropshell PRIVATE target_link_libraries(dropshell PRIVATE
libassert::assert
cpptrace::cpptrace
) )
# Install targets # Install targets

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

@ -19,8 +19,8 @@ _autocommandrun_volume() {
echo "Creating volume ${volume_name}" echo "Creating volume ${volume_name}"
docker volume create ${volume_name} docker volume create ${volume_name}
;; ;;
nuke) destroy)
echo "Nuking volume ${volume_name}" echo "Destroying volume ${volume_name}"
docker volume rm ${volume_name} docker volume rm ${volume_name}
;; ;;
backup) backup)
@ -50,14 +50,14 @@ _autocommandrun_path() {
echo "Creating path ${path}" echo "Creating path ${path}"
mkdir -p ${path} mkdir -p ${path}
;; ;;
nuke) destroy)
echo "Nuking path ${path}" echo "Destroying path ${path}"
local path_parent=$(dirname ${path}) local path_parent=$(dirname ${path})
local path_child=$(basename ${path}) local path_child=$(basename ${path})
if [ -d "${path_parent}/${path_child}" ]; then if [ -d "${path_parent}/${path_child}" ]; then
docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to nuke path ${path}" docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to destroy path ${path}"
else else
echo "Path ${path} does not exist - nothing to nuke" echo "Path ${path} does not exist - nothing to destroy"
fi fi
;; ;;
backup) backup)
@ -95,7 +95,7 @@ _autocommandrun_file() {
mkdir -p ${filepath_parent} mkdir -p ${filepath_parent}
fi fi
;; ;;
nuke) destroy)
rm -f ${filepath} rm -f ${filepath}
;; ;;
backup) backup)
@ -181,8 +181,8 @@ datacreate() {
} }
datanuke() { datadestroy() {
_autocommandparse nuke none "$@" _autocommandparse destroy none "$@"
} }
databackup() { databackup() {

View File

@ -5,7 +5,7 @@
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include <assert.hpp> #include <libassert/assert.hpp>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
@ -14,9 +14,7 @@ namespace autocomplete {
const std::set<std::string> system_commands_noargs = {"templates","autocomplete_list_servers","autocomplete_list_services","autocomplete_list_commands"}; const std::set<std::string> system_commands_noargs = {"templates","autocomplete_list_servers","autocomplete_list_services","autocomplete_list_commands"};
const std::set<std::string> system_commands_always_available = {"help","edit"}; const std::set<std::string> system_commands_always_available = {"help","edit"};
const std::set<std::string> system_commands_require_config = {"server","templates","create-service","create-template","create-server","ssh","list"}; const std::set<std::string> system_commands_require_config = {"server","templates","create-service","create-template","create-server","ssh","list"};
const std::set<std::string> system_commands_hidden = {"nuke","_allservicesstatus"}; const std::set<std::string> system_commands_hidden = {"_allservicesstatus"};
const std::set<std::string> service_commands_require_config = {"ssh","edit","nuke","_allservicesstatus"};
void merge_commands(std::set<std::string> &commands, const std::set<std::string> &new_commands) void merge_commands(std::set<std::string> &commands, const std::set<std::string> &new_commands)
{ {
@ -59,7 +57,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

@ -5,13 +5,13 @@
#include <filesystem> #include <filesystem>
#include "utils/output.hpp" #include "utils/output.hpp"
#include "utils/assert.hpp" #include <libassert/assert.hpp>
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "command_registry.hpp" #include "command_registry.hpp"
#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

@ -1,7 +1,7 @@
#include "command_registry.hpp" #include "command_registry.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/output.hpp" #include "utils/output.hpp"
#include "utils/assert.hpp" #include <libassert/assert.hpp>
namespace dropshell { namespace dropshell {

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 <libassert/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

@ -3,7 +3,7 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "utils/assert.hpp" #include <libassert/assert.hpp>
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "services.hpp" #include "services.hpp"
@ -67,7 +67,6 @@ 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::vector<std::string> variants_to_try = {"README.txt", "readme.txt", "ReadMe.txt", "README", "readme", "README.md", "readme.md"};
@ -94,17 +93,23 @@ namespace dropshell
std::string line; std::string line;
while (std::getline(readme_file, line)) while (std::getline(readme_file, line))
{ {
info << substitute_provided_key_value_pairs(line, all_env_vars) << std::endl; rawout << substitute_provided_key_value_pairs(line, all_env_vars) << std::endl;
} }
return true; return true;
} }
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override/*=""*/)
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()) if (server_name.empty() || template_name.empty() || service_name.empty())
return false; 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); std::string service_dir = localpath::service(server_name, service_name);
if (service_dir.empty()) if (service_dir.empty())
@ -146,6 +151,83 @@ namespace dropshell
// copy the template config files to the service directory // copy the template config files to the service directory
recursive_copy(tinfo.local_template_path() / "config", service_dir); 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; info << "Service " << service_name << " created successfully" << std::endl;
if (!print_readme(tinfo, server_name, service_name)) if (!print_readme(tinfo, server_name, service_name))
@ -158,6 +240,34 @@ namespace dropshell
return true; 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;
}
} // namespace shared_commands } // namespace shared_commands
} // namespace dropshell } // namespace dropshell

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 <libassert/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,42 +2,42 @@
#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"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/assert.hpp" #include <libassert/assert.hpp>
namespace dropshell namespace dropshell
{ {
int nuke_handler(const CommandContext &ctx); int destroy_handler(const CommandContext &ctx);
static std::vector<std::string> nuke_name_list = {"nuke"}; static std::vector<std::string> destroy_name_list = {"destroy"};
// Static registration // Static registration
struct NukeCommandRegister struct DestroyCommandRegister
{ {
NukeCommandRegister() DestroyCommandRegister()
{ {
CommandRegistry::instance().register_command({nuke_name_list, CommandRegistry::instance().register_command({destroy_name_list,
nuke_handler, destroy_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
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. Destroy 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.
@ -45,58 +45,64 @@ namespace dropshell
Use with caution! Use with caution!
)"}); )"});
} }
} nuke_command_register; } destroy_command_register;
namespace shared_commands namespace shared_commands
{ {
bool nuke_service(const std::string &server, const std::string &service) bool destroy_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 - destroy on remote server.
if (server_env.is_valid()) if (server_env.is_valid())
{ {
LocalServiceInfo service_info; LocalServiceInfo service_info;
service_info = get_service_info(server, service); service_info = get_service_info(server, service);
if (!SIvalid(service_info)) bool service_valid = SIvalid(service_info);
error << "Invalid service: " << service << std::endl; if (!service_valid)
warning << "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);
bool remote_dir_exists = server_env.check_remote_dir_exists(remotepath(server, user).service(service), user);
if (remote_dir_exists)
{ {
// run the nuke script on the remote server if it exists. // run the destroy script on the remote server if it exists.
// otherwise just uninstall. // otherwise just uninstall.
if (gTemplateManager().template_command_exists(service_info.template_name, "nuke")) if (service_valid)
{ {
info << "Running nuke script for " << service << " on " << server << std::endl; if (gTemplateManager().template_command_exists(service_info.template_name, "destroy"))
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {})) {
warning << "Failed to run nuke script: " << service << std::endl; info << "Running destroy script for " << service << " on " << server << std::endl;
if (!server_env.run_remote_template_command(service, "destroy", {}, false, {}))
warning << "Failed to run destroy script: " << service << std::endl;
} }
else else
{ {
info << "No nuke script found for " << service << " on " << server << std::endl; info << "No destroy script found for " << service << " on " << server << std::endl;
info << "Running uninstall script instead and will clean directories." << std::endl; info << "Running uninstall script instead and will clean directories." << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {})) if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Failed to uninstall service: " << service << std::endl; warning << "Failed to uninstall service: " << service << std::endl;
} }
}
// 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 destroy the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory. // step 2 - destroy the local service directory.
std::string local_service_path = localpath::service(server, service); std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path)) if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{ {
@ -109,15 +115,15 @@ namespace dropshell
error << "Failed to remove local service directory" << std::endl; error << "Failed to remove local service directory" << std::endl;
} }
info << "Nuked service " << service << " on server " << server << std::endl; info << "Destroyed service " << service << " on server " << server << std::endl;
return true; return true;
} }
} // namespace shared_commands } // namespace shared_commands
int nuke_handler(const CommandContext &ctx) int destroy_handler(const CommandContext &ctx)
{ {
ASSERT(ctx.args.size() == 2, "Usage: nuke SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")"); ASSERT(ctx.args.size() == 2, "Usage: destroy SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")");
ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration."); ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration.");
std::string server = safearg(ctx.args, 0); std::string server = safearg(ctx.args, 0);
@ -140,14 +146,14 @@ namespace dropshell
if (entry.is_directory() && entry.path().filename().string().find(".") != 0) if (entry.is_directory() && entry.path().filename().string().find(".") != 0)
{ {
std::string service_name = entry.path().filename().string(); std::string service_name = entry.path().filename().string();
rval |= (shared_commands::nuke_service(server, service_name) ? 0 : 1); rval |= (shared_commands::destroy_service(server, service_name) ? 0 : 1);
} }
} }
return rval; return rval;
} }
else else
{ {
return (shared_commands::nuke_service(server, service) ? 0 : 1); return (shared_commands::destroy_service(server, service) ? 0 : 1);
} }
} }

View File

@ -9,7 +9,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include "utils/assert.hpp" #include <libassert/assert.hpp>
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 "transwarp.hpp" #include "transwarp.hpp"

View File

@ -10,7 +10,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include "utils/assert.hpp" #include <libassert/assert.hpp>
namespace dropshell { namespace dropshell {
@ -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;
@ -55,6 +55,11 @@ void help_autocomplete(const CommandContext& ctx) {
} }
void show_command(const std::string& cmd) { void show_command(const std::string& cmd) {
// get console width
int width = get_console_width() - 6; // 5 for [INF] + 1 for space
int firstcol = 34;
int secondcol = width - firstcol - 3;
const auto& cmd_info = CommandRegistry::instance().find_command(cmd); const auto& cmd_info = CommandRegistry::instance().find_command(cmd);
if (!cmd_info) if (!cmd_info)
{ {
@ -62,9 +67,22 @@ void show_command(const std::string& cmd) {
return; return;
} }
info << " "; if (cmd_info->help_usage.length() < width-secondcol)
info << left_align(cmd_info->help_usage, 32); {
info << cmd_info->help_description << std::endl; std::string remaining_description = cmd_info->help_description;
info << " " << left_align(cmd_info->help_usage, firstcol) << get_line_wrap(remaining_description, secondcol);
while (!remaining_description.empty())
info << " " << left_align(" ",firstcol) << get_line_wrap(remaining_description, secondcol-1);
}
else
{
info << " " << cmd_info->help_usage << std::endl;
std::string remaining_description = cmd_info->help_description;
info << " " << left_align(" ",firstcol) << get_line_wrap(remaining_description, secondcol);
while (!remaining_description.empty())
info << " " << left_align(" ",firstcol) << get_line_wrap(remaining_description, secondcol-1);
}
} }
extern const std::string VERSION; extern const std::string VERSION;
@ -106,7 +124,7 @@ int help_handler(const CommandContext& ctx) {
if (ctx.args.size() > 0) if (ctx.args.size() > 0)
return show_command_help(ctx.args[0]); return show_command_help(ctx.args[0]);
info << std::endl; std::cout << std::endl;
maketitle("DropShell version " + VERSION); maketitle("DropShell version " + VERSION);
info << std::endl; info << std::endl;
info << "A tool for managing remote servers, by " << AUTHOR << std::endl; info << "A tool for managing remote servers, by " << AUTHOR << std::endl;
@ -120,16 +138,18 @@ int help_handler(const CommandContext& ctx) {
{ {
// show more! // show more!
show_command("list"); show_command("list");
std::cout << std::endl; info << std::endl;
show_command("install"); show_command("install");
show_command("uninstall"); show_command("uninstall");
show_command("nuke"); show_command("destroy");
std::cout << std::endl; info << std::endl;
show_command("start"); show_command("start");
show_command("stop"); show_command("stop");
std::cout << std::endl; info << std::endl;
show_command("ssh"); show_command("ssh");
std::cout << std::endl; info << std::endl;
show_command("create-server");
show_command("create-service");
} }
return 0; return 0;
} }

View File

@ -16,7 +16,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include "utils/assert.hpp" #include <libassert/assert.hpp>
#include "servers.hpp" #include "servers.hpp"
namespace dropshell namespace dropshell
@ -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()) {
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; 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;
}
server_env_manager server_env(server); std::string agent_path = remotepath(server.get_server_name(),user.user).agent();
if (!server_env.is_valid()) 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());
error << "Invalid server environment for " << server << std::endl;
return 1;
}
// now create the agent. // now create the agent.
// copy across from the local agent files. // copy across from the local agent files.
info << "Copying local agent files to remote server... " << std::flush; 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); shared_commands::rsync_tree_to_remote(localpath::agent_remote(), agent_path, server, false, user.user);
info << "done." << std::endl; info << "done." << std::endl;
// run the agent installer. Can't use BB64 yet, as we're installing it on the remote server. // 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); bool okay = execute_ssh_command(server.get_SSH_INFO(user.user), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr);
if (!okay) if (!okay)
{ {
error << "ERROR: Failed to install remote agent on " << server << std::endl; error << "ERROR: Failed to install remote agent on " << server.get_server_name() << std::endl;
return 1; return 1;
} }
info << "Installation on " << server << " complete." << std::endl; info << "Installation on " << server.get_server_name() << " 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>
@ -14,7 +14,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include "utils/assert.hpp" #include <libassert/assert.hpp>
namespace dropshell { namespace dropshell {
@ -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
{
std::lock_guard<std::mutex> lock(tp_mutex);
tp.add_row({sup.server.get_server_name(), sup.server.get_SSH_HOST(), sup.user.user, serviceticks, ports_used_str});
tp.add_row({server.name, server.ssh_user, server.ssh_host, serviceticks, ports_used_str});
++checked; ++checked;
// print out a tick character for each server checked. // print out a tick character for each server checked.
info << checked << "" << std::flush; 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,37 +153,41 @@ 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;
// // Get uptime if possible
// cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null";
// int rval = system(cmd.c_str());
// if (rval != 0) {
// std::cout << "Error: Failed to get uptime" << std::endl;
// }
} else { } else {
warning << "Status: Offline" << std::endl; warning << "Status: Offline" << std::endl;
} }
}
info << std::endl; info << std::endl;
//--------------------- //---------------------
{ {
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()) {
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.add_row({key, value});
} }
tp.print(); tp.print();
@ -164,7 +199,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);
std::set<int> ports_used; std::set<int> ports_used;
@ -176,7 +210,11 @@ void show_server_details(const std::string& server_name) {
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) + " ";
tp.add_row({healthy, service_name, get_service_info(server_name,service_name).template_name, ports_str}); 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, 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

@ -5,13 +5,13 @@
#include <filesystem> #include <filesystem>
#include "utils/output.hpp" #include "utils/output.hpp"
#include "utils/assert.hpp" #include <libassert/assert.hpp>
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "command_registry.hpp" #include "command_registry.hpp"
#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;
@ -175,39 +175,41 @@ namespace dropshell
info << "Backup complete." << std::endl; info << "Backup complete." << std::endl;
} }
{ // nuke the old service { // Destroy the old service
info << "2) Nuking old service..." << std::endl; info << "2) Destroying old service..." << std::endl;
if (!shared_commands::nuke_service(server, service)) if (!shared_commands::destroy_service(server, service))
return 1; return 1;
} }
{ // 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 <libassert/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,19 +110,24 @@ 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();
if (!execute_ssh_command(server_env.get_SSH_INFO(user),
sCommand(agentpath, "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", agentpath}}),
cMode::Silent, cMode::Silent,
&output)) &output))
return status; return status;
@ -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 destroy.cpp
bool nuke_service(const std::string &server, const std::string &service); bool destroy_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 <libassert/assert.hpp>
namespace dropshell namespace dropshell
{ {
@ -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;
@ -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); std::string service = safearg(ctx.args, 1);
// ssh into the specific service.
return ssh_into_service(server, service) ? 0 : 1; 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

@ -3,7 +3,7 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "utils/assert.hpp" #include <libassert/assert.hpp>
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "services.hpp" #include "services.hpp"
@ -36,40 +36,37 @@ namespace dropshell
uninstall SERVER SERVICE Uninstall the given service on the given server. uninstall SERVER SERVICE Uninstall the given service on the given server.
uninstall SERVER all Uninstall all services on the given server. uninstall SERVER all Uninstall all services on the given server.
Update and reinstall the service with install, or delete all configuration and data with nuke. Update and reinstall the service with install, or delete all configuration and data with destroy.
)"}); )"});
} }
} 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["template_registry_URLs"] = {
"https://templates.dropshell.app"
};
mConfig["template_local_paths"] = {
dropshell_base + "/local_templates"
};
mConfig["server_definition_paths"] = { mConfig["server_definition_paths"] = {
dropshell_base + "/servers" dropshell_base + "/servers"
}; };
mConfig["template_upload_registry_url"] = "https://templates.dropshell.app"; mConfig["template_local_paths"] = {
mConfig["template_upload_registry_token"] = "SECRETTOKEN"; dropshell_base + "/local_templates"
};
mConfig["template_registry_URLs"] = {
"https://templates.dropshell.app"
};
mConfig["template_upload_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))
{ {
@ -100,6 +98,11 @@ bool config::save_config(bool create_aux_directories)
} }
} }
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) {
if (url.is_string() && !url.empty())
urls.push_back(url); 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

@ -16,7 +16,7 @@
#include <vector> #include <vector>
#include <iomanip> #include <iomanip>
#include <chrono> #include <chrono>
#include <assert.hpp> #include <libassert/assert.hpp>
#include <sstream> #include <sstream>
#include <algorithm> #include <algorithm>
namespace dropshell { namespace dropshell {
@ -140,115 +140,6 @@ auto command_match = [](const std::string& cmd_list, int argc, char* argv[]) ->
} \ } \
} }
// int old_main(int argc, char* argv[]) {
// HAPPYEXIT("hash", hash_demo_raw(safearg(argc,argv,2)))
// HAPPYEXIT("version", printversion())
// BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2)))
// ASSERT(safearg(argc,argv,1) != "assert", "Hello! Here is an assert.");
// try {
// // silently attempt to load the config file and templates.
// gConfig().load_config();
// if (gConfig().is_config_set())
// gTemplateManager().load_sources();
// std::string cmd = argv[1];
// // ------------------------------------------------------------
// // from here we require the config file to be loaded.
// if (!gConfig().is_config_set())
// return die("Please run 'dropshell edit' to set up the dropshell configuration.");
// const std::vector<std::string> & server_definition_paths = gConfig().get_local_server_definition_paths();
// if (server_definition_paths.size()>1) { // only show if there are multiple.
// std::cout << "Server definition paths: ";
// for (auto & dir : server_definition_paths)
// std::cout << "["<< dir << "] ";
// std::cout << std::endl;
// }
// if (gTemplateManager().is_loaded() && gTemplateManager().get_source_count() > 0)
// gTemplateManager().print_sources();
// HAPPYEXIT("templates", gTemplateManager().list_templates());
// if (cmd == "create-template") {
// if (argc < 3) return die("Error: create-template requires a template name");
// return (gTemplateManager().create_template(argv[2])) ? 0 : 1;
// }
// if (cmd == "create-server") {
// if (argc < 3) return die("Error: create-server requires a server name");
// return (create_server(argv[2])) ? 0 : 1;
// }
// if (cmd == "create-service") {
// if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service");
// return (create_service(argv[2], argv[3], argv[4])) ? 0 : 1;
// }
// if (cmd == "ssh" && argc < 4) {
// if (argc < 3) return die("Error: ssh requires a server name and optionally service name");
// service_runner::interactive_ssh(argv[2], "bash");
// return 0;
// }
// // handle running a command.
// std::set<std::string> commands;
// get_all_used_commands(commands);
// autocomplete::merge_commands(commands, autocomplete::service_commands_require_config); // handled by service_runner, but not in template_shell_commands.
// if (commands.count(cmd)) {
// std::set<std::string> safe_commands = {"nuke", "fullnuke"};
// if (safe_commands.count(cmd) && argc < 4)
// return die("Error: "+cmd+" requires a server name and service name. For safety, can't run on all services.");
// // get all the services to run the command on.
// ServerAndServices server_and_services;
// if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services))
// return die("Error: "+cmd+" command requires server name and optionally service name");
// // run the command on each service.
// for (const auto& service_info : server_and_services.servicelist) {
// if (!SIvalid(service_info))
// std::cerr<<"Error: Unable to get service information."<<std::endl;
// else {
// service_runner runner(server_and_services.server_name, service_info.service_name);
// if (!runner.isValid())
// return die("Error: Failed to initialize service");
// std::vector<std::string> additional_args;
// for (int i=4; i<argc; i++)
// additional_args.push_back(argv[i]);
// if (!runner.run_command(cmd, additional_args))
// return die(cmd+" failed on service "+service_info.service_name);
// }
// }
// // success!
// return 0;
// }
// // Unknown command
// std::cerr << "Error: Unknown command '" << cmd << "'" << std::endl;
// std::cerr << "Valid commands: ";
// for (const auto& command : commands) {
// if (!command.empty() && command[0]!='_')
// std::cerr << command << " ";
// }
// std::cerr << std::endl;
// return 1;
// } catch (const std::exception& e) {
// std::cerr << "Error: " << e.what() << std::endl;
// return 1;
// }
// }
} // namespace dropshell } // namespace dropshell
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {

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,47 +1,379 @@
#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 <libassert/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::string server_json_path = localfile::server_json(server_name);
// Check if file exists
if (!std::filesystem::exists(server_json_path))
{
std::cerr << "Server environment file not found: " + server_json_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_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;
}
// get the variables from the json, converting everything to strings.
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_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(); std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty()) if (lsdp.empty())
return servers; return servers;
for (auto servers_dir : lsdp) { 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 (!servers_dir.empty() && std::filesystem::exists(servers_dir))
if (std::filesystem::is_directory(entry)) { {
for (const auto &entry : std::filesystem::directory_iterator(servers_dir))
{
if (std::filesystem::is_directory(entry))
{
std::string server_name = entry.path().filename().string(); std::string server_name = entry.path().filename().string();
if (server_name.empty() || server_name[0] == '.' || server_name[0] == '_') if (server_name.empty() || server_name[0] == '.' || server_name[0] == '_')
continue; continue;
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: " << entry.path().string() << std::endl; std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl;
continue; continue;
} }
servers.push_back({ servers.push_back(env);
server_name,
env.get_SSH_HOST(),
env.get_SSH_USER(),
env.get_SSH_PORT()
});
} }
} }
} }
@ -50,42 +382,23 @@ std::vector<ServerInfo> get_configured_servers() {
return servers; return servers;
} }
ServerInfo get_server_info(const std::string &server_name)
{
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty())
return ServerInfo();
for (auto &config_dir : lsdp) {
std::string server_dir = config_dir + "/" + server_name;
if (std::filesystem::exists(server_dir)) {
server_env_manager env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << server_dir << std::endl;
continue;
}
return ServerInfo({server_name, env.get_SSH_HOST(), env.get_SSH_USER(), env.get_SSH_PORT()});
}
}
return ServerInfo();
}
bool create_server(const std::string &server_name) bool create_server(const std::string &server_name)
{ {
// 1. check if server name already exists // 1. check if server name already exists
std::string server_existing_dir = localpath::server(server_name); std::string server_existing_dir = localpath::server(server_name);
if (!server_existing_dir.empty()) { 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; error << "Error: Server name already exists: " << server_name << std::endl;
info << "Current server path: " << server_existing_dir << std::endl;
return false; return false;
} }
// 2. create a new directory in the user config directory // 2. create a new directory in the user config directory
auto lsdp = gConfig().get_local_server_definition_paths(); auto lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty() || lsdp[0].empty()) { 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; error << "Error: Local server definition path not found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false; return false;
} }
std::string server_dir = lsdp[0] + "/" + server_name; std::string server_dir = lsdp[0] + "/" + server_name;
@ -93,33 +406,48 @@ bool create_server(const std::string &server_name)
// 3. create a template server.env file in the server directory // 3. create a template server.env file in the server directory
std::string user = getenv("USER"); std::string user = getenv("USER");
std::string server_env_path = server_dir + "/server.env"; std::string server_env_path = server_dir + "/" + filenames::server_json;
std::ofstream server_env_file(server_env_path); std::ofstream server_env_file(server_env_path);
server_env_file << "SSH_HOST=" << server_name << std::endl; server_env_file << "{" << std::endl;
server_env_file << "SSH_USER=" << user << std::endl; server_env_file << " \"SSH_HOST\": \"" << server_name << "\"," << std::endl;
server_env_file << "SSH_PORT=" << 22 << std::endl; server_env_file << " \"SSH_PORT\": " << 22 << "," << std::endl;
server_env_file << std::endl; server_env_file << " \"SSH_USERS\": [" << std::endl;
server_env_file << "DROPSHELL_DIR=/home/"+user+"/.dropshell" << std::endl; 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(); server_env_file.close();
std::cout << "Server created successfully: " << server_name << std::endl; std::cout << "Server created successfully: " << server_name << std::endl;
std::cout << "Please complete the installation:" << 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 << "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 << "2) install the server: dropshell install " << server_name << std::endl;
std::cout << "3) install the server: dropshell install " << server_name << std::endl;
std::cout << std::endl; std::cout << std::endl;
return true; return true;
} }
void get_all_used_commands(std::set<std::string> &commands) void get_all_used_commands(std::set<std::string> &commands)
{ {
std::vector<ServerInfo> servers = get_configured_servers(); std::vector<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
auto services = dropshell::get_server_services_info(server.name); auto services = get_server_services_info(server.get_server_name());
for (const auto &service : services) for (const auto &service : services)
commands.merge(dropshell::get_used_commands(server.name, service.service_name)); commands.merge(get_used_commands(server.get_server_name(), service.service_name));
} }
} }
bool server_exists(const std::string &server_name)
{
std::string server_existing_dir = localpath::server(server_name);
if (server_existing_dir.empty())
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,117 @@
#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 <libassert/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 fs = std::filesystem;
// namespace dropshell {
namespace dropshell {
// 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)
// {
// if (server_name.empty() || service_name.empty())
// return;
// // Initialize server environment
// if (!mServerEnv.is_valid())
// return;
class service_runner { // mServiceInfo = get_service_info(server_name, service_name);
public: // if (mServiceInfo.service_name.empty())
service_runner(const std::string& server_name, const std::string& service_name); // return;
bool isValid() const { return mValid; }
// run a command over ssh, using the credentials from server.env (via server_env.hpp) // mService = mServiceInfo.service_name;
// 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
// do a lot of checks, such as:
// checking that we can ssh to the server.
// checking whether the service directory exists on the server.
// checking that the command exists in the service directory.
// checking that the command is a valid .sh file.
// 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={});
// check health of service. Silent. // mValid = !mServiceInfo.local_template_path.empty();
// 1. run status.sh on the server // }
// 2. return the output of the status.sh script
//HealthStatus is_healthy();
// std::string healthtick();
// std::string healthmark();
public:
// backup and restore
bool backup(bool silent=false);
bool restore(std::string backup_file, bool silent=false);
// nuke the service // // ------------------------------------------------------------------------------------------------
bool nuke(bool silent=false); // nukes all data for this service on the remote server // // Run a command on the service.
bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles // // ------------------------------------------------------------------------------------------------
// bool service_runner::run_command(const std::string& command, std::vector<std::string> additional_args, std::map<std::string, std::string> env_vars) {
// launch an interactive ssh session on a server or service // if (!mServerEnv.is_valid()) {
// replaces the current dropshell process with the ssh process // std::cerr << "Error: Server service not initialized" << std::endl;
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_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false);
public:
// utility functions
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 std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
private:
std::string mServer;
server_env_manager mServerEnv;
LocalServiceInfo mServiceInfo;
std::string mService;
bool mValid;
// Helper methods
public:
};
} // namespace dropshell
namespace fs = std::filesystem;
namespace dropshell {
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)
{
if (server_name.empty() || service_name.empty())
return;
// Initialize server environment
if (!mServerEnv.is_valid())
return;
mServiceInfo = get_service_info(server_name, service_name);
if (mServiceInfo.service_name.empty())
return;
mService = mServiceInfo.service_name;
mValid = !mServiceInfo.local_template_path.empty();
}
bool service_runner::nuke(bool silent)
{
maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);
if (!mServerEnv.is_valid()) return false; // should never hit this.
std::string remote_service_path = remotepath::service(mServer, mService);
info << "Service " << mService << " successfully nuked from " << mServer << std::endl;
if (!silent) {
info << "There's nothing left on the remote server." << std::endl;
info << "You can remove the local files with:" << std::endl;
info << " rm -rf " << localpath::service(mServer,mService) << std::endl;
}
return true;
}
bool service_runner::fullnuke()
{
if (!nuke(true))
{
warning << "Nuke script failed, aborting." << std::endl;
return false;
}
std::string local_service_path = mServiceInfo.local_service_path;
if (local_service_path.empty() || !fs::exists(local_service_path)) {
error << "Service directory not found: " << local_service_path << std::endl;
return false;
}
std::string rm_cmd = "rm -rf " + quote(local_service_path);
if (!execute_local_command("", rm_cmd, {}, nullptr, cMode::Silent)) {
error << "Failed to remove service directory" << std::endl;
return false;
}
return true;
}
// ------------------------------------------------------------------------------------------------
// 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) {
if (!mServerEnv.is_valid()) {
std::cerr << "Error: Server service not initialized" << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(mServiceInfo.template_name);
if (!tinfo.is_set()) {
std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl;
return false;
}
if (command == "fullnuke")
return fullnuke();
if (command == "nuke")
{
std::cout << "Nuking " << mService << " (" << mServiceInfo.template_name << ") on " << mServer << std::endl;
return nuke();
}
if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) {
std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
return true; // nothing to run.
}
// install doesn't require anything on the server yet.
// if (command == "install")
// return install_service(mServer, mService, false);
std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh";
// Check if service directory exists
if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
std::cerr << "Error: Service is not installed: " << mService << std::endl;
return false;
}
// Check if command script exists
if (!mServerEnv.check_remote_file_exists(script_path)) {
std::cerr << "Error: Remote command script not found: " << script_path << std::endl;
return false;
}
// Check if env file exists
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;
return false;
}
// if (command == "uninstall")
// return uninstall();
if (command == "ssh") {
interactive_ssh_service();
return true;
}
if (command == "restore") {
if (additional_args.size() < 1) {
std::cerr << "Error: restore requires a backup file:" << std::endl;
std::cerr << "dropshell restore <server> <service> <backup-file>" << std::endl;
return false;
}
return restore(additional_args[0], false);
}
if (command == "backup") {
return backup(false);
}
// Run the generic command
std::vector<std::string> args; // not passed through yet.
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) {
std::string serverpath = localpath::server(server_name);
if (serverpath.empty()) {
std::cerr << "Error: Server not found: " << server_name << std::endl;
return false;
}
server_env_manager env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
return false;
}
sCommand scommand("", "bash",{});
return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
}
bool service_runner::interactive_ssh_service()
{
std::set<std::string> used_commands = get_used_commands(mServer, mService);
if (used_commands.find("ssh") == used_commands.end()) {
std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
return false;
}
std::vector<std::string> args; // not passed through yet.
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)
{
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" : "");
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)
{
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" : "");
return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
bool service_runner::restore(std::string backup_file, bool silent)
{
if (backup_file.empty()) {
std::cerr << "Error: not enough arguments. dropshell restore <server> <service> <backup-file>" << std::endl;
return false;
}
std::string local_backups_dir = gConfig().get_local_backup_path();
if (backup_file == "latest") {
// get the latest backup file from the server
backup_file = get_latest_backup_file(mServer, mService);
}
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string();
if (! std::filesystem::exists(local_backup_file_path)) {
std::cerr << "Error: Backup file not found at " << local_backup_file_path << std::endl;
return false;
}
// split the backup filename into parts based on the magic string
std::vector<std::string> parts = dropshell::split(backup_file, "-_-");
if (parts.size() != 4) {
std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
return false;
}
std::string backup_server_name = parts[0];
std::string backup_template_name = parts[1];
std::string backup_service_name = parts[2];
std::string backup_datetime = parts[3];
if (backup_template_name != mServiceInfo.template_name) {
std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl;
return false;
}
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 << std::endl;
std::cout << "*** ALL DATA FOR "<<mServer<<"/"<<mService<<" WILL BE OVERWRITTEN! ***"<<std::endl;
// run the restore script
std::cout << "OK, here goes..." << std::endl;
{ // backup existing service
maketitle("1) Backing up old service... ");
if (!backup(true)) // silent=true
{
std::cerr << 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 << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl;
return false;
}
std::cout << "Backup complete." << std::endl;
}
{ // uninstall service, then nuke it.
maketitle("2) Uninstalling old service...");
// if (!uninstall(true))
// return false; // return false;
// }
maketitle("3) Nuking old service..."); // template_info tinfo = gTemplateManager().get_template_info(mServiceInfo.template_name);
// if (!nuke(true)) // if (!tinfo.is_set()) {
// return false; // std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl;
}
{ // restore service from backup
maketitle("4) Restoring service data from backup...");
std::string remote_backups_dir = remotepath::backups(mServer);
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// Copy backup file from local to server
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;
return false;
}
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()}});
} // dtor of remote_temp_folder will clean up the temp folder on the server
// { // installing fresh service
// maketitle("5) Non-destructive install of fresh service...");
// if (!install_service(mServer, mService, true))
// return false; // return false;
// } // }
bool healthy = false; // if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) {
{// healthcheck the service // std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
maketitle("6) Healthchecking service..."); // return true; // nothing to run.
std::string green_tick = "\033[32m✓\033[0m"; // }
std::string red_cross = "\033[31m✗\033[0m";
healthy= (mServerEnv.run_remote_template_command(mService, "status", {}, silent, {}));
if (!silent)
std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
}
return healthy; // // install doesn't require anything on the server yet.
} // // if (command == "install")
// // return install_service(mServer, mService, false);
// std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh";
// // Check if service directory exists
// if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
// std::cerr << "Error: Service is not installed: " << mService << std::endl;
// return false;
// }
// // Check if command script exists
// if (!mServerEnv.check_remote_file_exists(script_path)) {
// std::cerr << "Error: Remote command script not found: " << script_path << std::endl;
// return false;
// }
// // Check if env file exists
// 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;
// return false;
// }
// // if (command == "uninstall")
// // return uninstall();
// if (command == "ssh") {
// interactive_ssh_service();
// return true;
// }
// if (command == "restore") {
// if (additional_args.size() < 1) {
// std::cerr << "Error: restore requires a backup file:" << std::endl;
// std::cerr << "dropshell restore <server> <service> <backup-file>" << std::endl;
// return false;
// }
// return restore(additional_args[0], false);
// }
// if (command == "backup") {
// return backup(false);
// }
// // Run the generic command
// std::vector<std::string> args; // not passed through yet.
// return mServerEnv.run_remote_template_command(mService, command, args, false, env_vars);
// }
// backup the service over ssh, using the credentials from server.env (via server_env.hpp) // } // namespace dropshell
// 1. run backup.sh on the server
// 2. create a backup file with format server-service-datetime.tgz
// 3. store it in the server's DROPSHELL_DIR/backups folder
// 4. copy it to the local user_dir/backups folder
// ------------------------------------------------------------------------------------------------
// Backup the service.
// ------------------------------------------------------------------------------------------------
bool service_runner::backup(bool silent) {
auto service_info = get_service_info(mServer, mService);
if (service_info.local_service_path.empty()) {
std::cerr << "Error: Service not found" << std::endl;
return 1;
}
const std::string command = "backup";
if (!gTemplateManager().template_command_exists(service_info.template_name, command)) {
std::cout << "No backup script for " << service_info.template_name << std::endl;
return true; // nothing to back up.
}
// Check if basic installed stuff is in place.
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_service_config_path = remotepath::service_config(mServer, mService);
if (!mServerEnv.check_remote_items_exist({
remotepath::service(mServer, mService),
remote_command_script_file,
remotefile::service_env(mServer, mService)})
)
{
std::cerr << "Error: Required service directories not found on remote server" << std::endl;
std::cerr << "Is the service installed?" << std::endl;
return false;
}
// Create backups directory on server if it doesn't exist
std::string remote_backups_dir = remotepath::backups(mServer);
if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Silent)) {
std::cerr << "Failed to create backups directory on server" << std::endl;
return false;
}
// Create backups directory locally if it doesn't exist
std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty()) {
std::cerr << "Error: Local backups directory not found" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false;
}
if (!std::filesystem::exists(local_backups_dir))
std::filesystem::create_directories(local_backups_dir);
// Get current datetime for backup filename
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::stringstream datetime;
datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
// Construct backup filename
shared_commands::cBackupFileName backup_filename_construction(mServer, mService, service_info.template_name);
if (!backup_filename_construction.is_valid()) {
std::cerr << "Invalid backup filename" << std::endl;
return false;
}
std::string backup_filename = backup_filename_construction.get_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();
// 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");
{ // Run backup script
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()}})) {
std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl;
return false;
}
// Copy backup file from server to local
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;
return false;
}
} // dtor of remote_temp_folder will clean up the temp folder on the server
if (!silent) {
std::cout << "Backup created successfully. Restore with:"<<std::endl;
std::cout << " dropshell restore " << mServer << " " << mService << " " << backup_filename << std::endl;
}
return true;
}
// 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 local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return "";
}
// Get the template name for this service
LocalServiceInfo info = get_service_info(server, service);
if (info.template_name.empty()) {
std::cerr << "Error: Could not determine template name for service: " << service << std::endl;
return "";
}
// Build the expected prefix for backup files
std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-";
std::string latest_file;
std::string latest_datetime;
std::cout << "Looking for backup files in " << local_backups_dir << std::endl;
for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
if (!entry.is_regular_file()) continue;
std::string filename = entry.path().filename().string();
if (filename.rfind(prefix, 0) == 0) { // starts with prefix
// Extract the datetime part
size_t dt_start = prefix.size();
size_t dt_end = filename.find(".tgz", dt_start);
if (dt_end == std::string::npos) continue;
std::string datetime = filename.substr(dt_start, dt_end - dt_start);
std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl;
if (datetime > latest_datetime) {
latest_datetime = datetime;
latest_file = filename;
}
}
}
if (latest_file.empty()) {
std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl;
}
std::cout << "Latest backup file: " << latest_file << std::endl;
return latest_file;
}
} // namespace dropshell

View File

@ -4,42 +4,49 @@
#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 <libassert/assert.hpp>
#include "servers.hpp"
#include "assert.hpp"
#include <iostream> #include <iostream>
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace dropshell { namespace dropshell
{
bool SIvalid(const LocalServiceInfo& service_info) { #pragma message("TODO : Smart test that the service is fully valid.")
bool SIvalid(const LocalServiceInfo &service_info)
{
return !service_info.service_name.empty() && return !service_info.service_name.empty() &&
!service_info.template_name.empty() && !service_info.template_name.empty() &&
!service_info.local_service_path.empty() && !service_info.local_service_path.empty() &&
!service_info.local_template_path.empty(); !service_info.local_template_path.empty() &&
!service_info.user.empty();
} }
std::vector<LocalServiceInfo> get_server_services_info(const std::string& server_name) { std::vector<LocalServiceInfo> get_server_services_info(const std::string &server_name)
{
std::vector<LocalServiceInfo> services; std::vector<LocalServiceInfo> services;
if (server_name.empty()) if (server_name.empty())
return services; return services;
std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths(); std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
if (local_server_definition_paths.empty()) { if (local_server_definition_paths.empty())
{
std::cerr << "Error: No local server definition paths found" << std::endl; std::cerr << "Error: No local server definition paths found" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl; std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return services; return services;
} }
for (const auto& server_definition_path : local_server_definition_paths) { for (const auto &server_definition_path : local_server_definition_paths)
{
fs::path serverpath = server_definition_path + "/" + server_name; fs::path serverpath = server_definition_path + "/" + server_name;
if (fs::exists(serverpath)) // service is on that server... if (fs::exists(serverpath)) // service is on that server...
for (const auto& entry : fs::directory_iterator(serverpath)) { for (const auto &entry : fs::directory_iterator(serverpath))
if (fs::is_directory(entry)) { {
if (fs::is_directory(entry))
{
std::string dirname = entry.path().filename().string(); std::string dirname = entry.path().filename().string();
if (dirname.empty() || dirname[0] == '.' || dirname[0] == '_') if (dirname.empty() || dirname[0] == '.' || dirname[0] == '_')
continue; continue;
@ -55,6 +62,16 @@ std::vector<LocalServiceInfo> get_server_services_info(const std::string& server
return services; return services;
} }
bool get_bool_variable(const std::map<std::string, std::string> &variables, const std::string &variable_name)
{
auto it = variables.find(variable_name);
if (it == variables.end())
{
error << "Variable " << variable_name << " not found in the service " << filenames::template_info_env << std::endl;
return false;
}
return it->second == "true";
}
LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name) LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name)
{ {
@ -81,23 +98,53 @@ LocalServiceInfo get_service_info(const std::string &server_name, const std::str
if (!get_all_service_env_vars(server_name, service_name, variables)) if (!get_all_service_env_vars(server_name, service_name, variables))
return LocalServiceInfo(); return LocalServiceInfo();
// confirm TEMPLATE is defined. { // confirm TEMPLATE is defined.
auto it = variables.find("TEMPLATE"); auto it = variables.find("TEMPLATE");
if (it == variables.end()) { if (it == variables.end())
std::cerr << "Error: TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl; {
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
return LocalServiceInfo(); return LocalServiceInfo();
} }
service.template_name = it->second; service.template_name = it->second;
}
template_info tinfo = gTemplateManager().get_template_info(service.template_name); template_info tinfo = gTemplateManager().get_template_info(service.template_name);
if (!tinfo.is_set()) { if (!tinfo.is_set())
std::cerr << "Error: Template '" << service.template_name << "' not found" << std::endl; {
error << "Template specified '" << service.template_name << "' could not be found" << std::endl;
return LocalServiceInfo(); return LocalServiceInfo();
} }
// find the template path // find the template path
service.local_template_path = tinfo.local_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; return service;
} }
@ -109,14 +156,16 @@ std::set<std::string> get_used_commands(const std::string &server_name, const st
return commands; return commands;
auto service_info = get_service_info(server_name, service_name); auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty()) { if (service_info.local_template_path.empty())
{
std::cerr << "Error: Service not found: " << service_name << std::endl; std::cerr << "Error: Service not found: " << service_name << std::endl;
return commands; return commands;
} }
// iterate over all files in the template path, and add the command name to the set. // 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 _ // commands are .sh files that don't begin with _
for (const auto& entry : fs::directory_iterator(service_info.local_template_path)) { 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)) if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0))
commands.insert(entry.path().stem().string()); commands.insert(entry.path().stem().string());
} }
@ -132,17 +181,20 @@ std::set<std::string> list_backups(const std::string &server_name, const std::st
// need to find the template for the service. // need to find the template for the service.
auto service_info = get_service_info(server_name, service_name); auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty()) { if (service_info.local_template_path.empty())
{
std::cerr << "Error: Service not found: " << service_name << std::endl; std::cerr << "Error: Service not found: " << service_name << std::endl;
return backups; return backups;
} }
std::string backups_dir = gConfig().get_local_backup_path(); std::string backups_dir = localpath::backups();
if (backups_dir.empty()) if (backups_dir.empty())
return backups; return backups;
if (fs::exists(backups_dir)) { if (fs::exists(backups_dir))
for (const auto& entry : fs::directory_iterator(backups_dir)) { {
for (const auto &entry : fs::directory_iterator(backups_dir))
{
if (fs::is_regular_file(entry) && entry.path().extension() == ".tgz") if (fs::is_regular_file(entry) && entry.path().extension() == ".tgz")
if (entry.path().filename().string().find(service_info.template_name) != std::string::npos) if (entry.path().filename().string().find(service_info.template_name) != std::string::npos)
{ {
@ -153,8 +205,6 @@ std::set<std::string> list_backups(const std::string &server_name, const std::st
return backups; 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) 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(); all_env_vars.clear();
@ -165,22 +215,11 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
return false; return false;
} }
ServerInfo server_info = get_server_info(server_name);
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 // Lambda function to load environment variables from a file
auto load_env_file = [&all_env_vars](const std::string& file) { auto load_env_file = [&all_env_vars](const std::string &file)
if (!file.empty() && std::filesystem::exists(file)) { {
if (!file.empty() && std::filesystem::exists(file))
{
std::map<std::string, std::string> env_vars; std::map<std::string, std::string> env_vars;
envmanager env_manager(file); envmanager env_manager(file);
env_manager.load(); env_manager.load();
@ -195,28 +234,41 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
load_env_file(localfile::service_env(server_name, service_name)); load_env_file(localfile::service_env(server_name, service_name));
load_env_file(localfile::template_info_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. // determine template name.
auto it = all_env_vars.find("TEMPLATE"); auto it = all_env_vars.find("TEMPLATE");
if (it == all_env_vars.end()) { if (it == all_env_vars.end())
{
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl; 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 << "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 << "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; info << " " << localpath::service(server_name, service_name) << std::endl
<< std::endl;
return false; return false;
} }
template_info tinfo = gTemplateManager().get_template_info(it->second); template_info tinfo = gTemplateManager().get_template_info(it->second);
if (!tinfo.is_set()) { if (!tinfo.is_set())
{
std::cerr << "Error: Template '" << it->second << "' not found" << std::endl; std::cerr << "Error: Template '" << it->second << "' not found" << std::endl;
return false; return false;
} }
std::string default_env_file = tinfo.local_template_path()/"_default.env";
if (!fs::exists(default_env_file)) {
std::cerr << "Error: Template default env file '" << default_env_file << "' not found" << std::endl;
return false;
}
load_env_file(default_env_file);
return true; return true;
} }

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

@ -6,13 +6,14 @@
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
#include <map> #include <map>
#include "utils/assert.hpp" #include <libassert/assert.hpp>
#include "utils/envmanager.hpp" #include "utils/envmanager.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#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

@ -1,12 +0,0 @@
#ifndef ASSERT_HPP
#define ASSERT_HPP
#include "output.hpp"
#define ASSERT(condition, message) \
if (!(condition)) { \
dropshell::error << "Assertion failed: " << message << std::endl; \
std::exit(1); \
}
#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;
} }
remotepath::remotepath(const std::string &server_name, const std::string &user) : mServer_name(server_name), mUser(user) {}
std::string remotepath::DROPSHELL_DIR() const
{
return ServerConfig(mServer_name).get_user_dir(mUser);
} }
namespace remotepath { std::string remotepath::services() const
std::string DROPSHELL_DIR(const std::string &server_name)
{ {
return server_env_manager(server_name).get_DROPSHELL_DIR(); std::string dsp = DROPSHELL_DIR();
}
std::string services(const std::string &server_name)
{
std::string dsp = DROPSHELL_DIR(server_name);
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,9 +14,26 @@ namespace dropshell {
// ~/.config/dropshell/dropshell.json // ~/.config/dropshell/dropshell.json
// ~/.local/dropshell_agent // ~/.local/dropshell_agent
// |-- agent-local
// |-- agent-install.sh
// |-- bb64 (only used locally, as it's for the local machine's architecture!) // |-- bb64 (only used locally, as it's for the local machine's architecture!)
// |-- files_for_remote_agent // |-- template_example
// |-- (other agent files, including _allservicesstatus.sh) // |-- 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>
@ -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

@ -6,7 +6,7 @@
#include <string> #include <string>
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include "utils/assert.hpp" #include <libassert/assert.hpp>
#include "execute.hpp" #include "execute.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
@ -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 {
public:
sSSHInfo(std::string host, std::string user, std::string port, std::string server_ID, std::string user_dir) :
host(host), user(user), port(port), server_ID(server_ID), user_dir(user_dir) {}
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 host;
std::string user; std::string user;
std::string port; std::string port;
std::string server_ID; // dropshell name for server. std::string server_ID; // dropshell name for server.
} sSSHInfo; 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

@ -42,7 +42,7 @@ namespace dropshell
std::lock_guard<std::mutex> lock(output_mutex); std::lock_guard<std::mutex> lock(output_mutex);
if (c == EOF) if (c == EOF)
return !EOF; return !EOF;
if (at_line_start_ && c != '\n') if (at_line_start_) // && c != '\n')
{ {
dest_ << GREY << tag_ << RESET << ' ' << colour_; dest_ << GREY << tag_ << RESET << ' ' << colour_;
at_line_start_ = false; at_line_start_ = false;

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

@ -7,6 +7,9 @@
#include <filesystem> #include <filesystem>
#include <regex> #include <regex>
#include <random> #include <random>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cctype>
namespace dropshell { namespace dropshell {
@ -388,4 +391,58 @@ std::string substitute_provided_key_value_pairs(std::string str, const std::map<
return result; return result;
} }
int get_console_width()
{
struct winsize w;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
return w.ws_col;
}
// Fallback to a reasonable default if we can't get the width
return 80;
}
std::string remove_return(std::string str)
{
str.erase(std::remove(str.begin(), str.end(), '\n'), str.end());
return str;
}
std::string get_line_wrap(std::string &src, int maxchars)
{
if (src.empty())
return "";
if (src.length() <= maxchars)
{
std::string out = src;
src.erase();
return remove_return(out) + '\n';
}
// find last whitespace up to but not more than maxchars
size_t grab_to=maxchars;
size_t lastreturn = src.rfind('\n', maxchars);
size_t lastspace = src.rfind(' ', maxchars);
if (lastreturn != std::string::npos)
grab_to = lastreturn;
else if (lastspace != std::string::npos)
grab_to = lastspace;
std::string out = src.substr(0, grab_to);
src = src.substr(grab_to + 1);
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

@ -55,4 +55,10 @@ std::string center_align(const std::string & str, int width);
std::string replace_with_environment_variables_like_bash(std::string str); std::string replace_with_environment_variables_like_bash(std::string str);
std::string substitute_provided_key_value_pairs(std::string str, const std::map<std::string, std::string> & env_vars); std::string substitute_provided_key_value_pairs(std::string str, const std::map<std::string, std::string> & env_vars);
int get_console_width();
std::string get_line_wrap(std::string & src, int maxchars);
std::string tolower(const std::string& str);
} // namespace dropshell } // namespace dropshell