Compare commits

...

5 Commits

Author SHA1 Message Date
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
22 changed files with 821 additions and 556 deletions

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

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

@ -102,7 +102,7 @@ namespace dropshell
} }
// 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;

View File

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

View File

@ -94,7 +94,7 @@ 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;
} }

View File

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

View File

@ -14,7 +14,7 @@ namespace dropshell
{ {
int nuke_handler(const CommandContext &ctx); int nuke_handler(const CommandContext &ctx);
static std::vector<std::string> nuke_name_list = {"nuke"}; static std::vector<std::string> nuke_name_list = {"destroy","nuke"};
// Static registration // Static registration
struct NukeCommandRegister struct NukeCommandRegister
@ -29,15 +29,15 @@ namespace dropshell
true, // requires_install true, // requires_install
2, // min_args (after command) 2, // min_args (after command)
2, // max_args (after command) 2, // max_args (after command)
"nuke SERVER SERVICE|all", "destroy SERVER SERVICE|all",
"Nuke a service on a server. Destroys everything, both local and remote!", "Destroy a service on a server. Erases everything, both local and remote!",
// heredoc // heredoc
R"( R"(
Nuke a service. Nuke a service.
Examples: Examples:
nuke SERVER SERVICE nuke the given service on the given server. destroy SERVER SERVICE destroy the given service on the given server.
nuke SERVER all nuke all services on the given server. destroy SERVER all destroy all services on the given server.
Note: This command is destructive and will destroy all data and all configuration, Note: This command is destructive and will destroy all data and all configuration,
both on the dropshell host and on the remote server. both on the dropshell host and on the remote server.

View File

@ -45,7 +45,7 @@ struct HelpCommandRegister {
void help_autocomplete(const CommandContext& ctx) { void help_autocomplete(const CommandContext& ctx) {
if (ctx.args.size() == 1) { if (ctx.args.size() == 0) {
// list all commands // list all commands
for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) { for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) {
rawout << cmd << std::endl; rawout << cmd << std::endl;
@ -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("nuke");
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

@ -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
@ -238,30 +238,22 @@ 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;
} }
@ -288,7 +280,7 @@ namespace dropshell
// 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_env, false);
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.

View File

@ -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;
@ -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;

View File

@ -46,6 +46,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 +70,27 @@ bool config::save_config(bool create_aux_directories)
if (!mIsConfigSet) if (!mIsConfigSet)
{ {
std::string homedir = localpath::current_user_home(); std::string homedir = localpath::current_user_home();
std::string dropshell_base = homedir + "/.dropshell"; std::string dropshell_base = homedir + "/.local/dropshell_files";
mConfig["tempfiles"] = dropshell_base + "/tmp";
mConfig["backups"] = dropshell_base + "/backups";
mConfig["template_cache"] = dropshell_base + "/template_cache"; mConfig["server_definition_paths"] = {
mConfig["template_registry_URLs"] = { dropshell_base + "/servers"
"https://templates.dropshell.app"
}; };
mConfig["template_local_paths"] = { mConfig["template_local_paths"] = {
dropshell_base + "/local_templates" dropshell_base + "/local_templates"
}; };
mConfig["template_registry_URLs"] = {
mConfig["server_definition_paths"] = { "https://templates.dropshell.app"
dropshell_base + "/servers" };
}; mConfig["template_upload_token"] = "SECRETTOKEN";
mConfig["template_upload_registry_url"] = "https://templates.dropshell.app";
mConfig["template_upload_registry_token"] = "SECRETTOKEN";
} }
config_file << mConfig.dump(4); config_file << mConfig.dump(4);
config_file.close(); config_file.close();
if (create_aux_directories) { if (create_aux_directories) {
std::vector<std::filesystem::path> paths = { std::vector<std::string> paths;
get_local_template_cache_path(), _append(paths, get_local_template_paths());
get_local_backup_path(), _append(paths, get_local_server_definition_paths());
get_local_tempfiles_path()
};
for (auto & p : get_local_server_definition_paths())
paths.push_back(p);
for (auto & p : paths) for (auto & p : paths)
if (!std::filesystem::exists(p)) if (!std::filesystem::exists(p))
{ {
@ -99,6 +98,11 @@ bool config::save_config(bool create_aux_directories)
std::filesystem::create_directories(p); std::filesystem::create_directories(p);
} }
} }
debug << "Config paths: " << std::endl;
for (auto [key,value] : mConfig.items()) {
debug << " " << key << ": " << value << std::endl;
}
return true; return true;
} }
@ -110,31 +114,20 @@ bool config::is_config_set() const
bool config::is_agent_installed() bool config::is_agent_installed()
{ {
return std::filesystem::exists(localpath::agent() + "/bb64"); return std::filesystem::exists(localfile::bb64());
}
std::string config::get_local_tempfiles_path() {
return mConfig["tempfiles"];
}
std::string config::get_local_backup_path() {
return mConfig["backups"];
}
std::string config::get_local_template_cache_path() {
return mConfig["template_cache"];
} }
std::vector<std::string> config::get_template_registry_urls() { std::vector<std::string> config::get_template_registry_urls() {
nlohmann::json template_registry_urls = mConfig["template_registry_URLs"]; nlohmann::json template_registry_urls = mConfig["template_registry_URLs"];
std::vector<std::string> urls; std::vector<std::string> urls;
for (auto &url : template_registry_urls) { for (auto &url : template_registry_urls) {
urls.push_back(url); if (url.is_string() && !url.empty())
urls.push_back(url);
} }
return urls; return urls;
} }
std::vector<std::string> config::get_template_local_paths() std::vector<std::string> config::get_local_template_paths()
{ {
nlohmann::json template_local_paths = mConfig["template_local_paths"]; nlohmann::json template_local_paths = mConfig["template_local_paths"];
std::vector<std::string> paths; std::vector<std::string> paths;
@ -147,23 +140,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

@ -17,15 +17,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

@ -7,6 +7,7 @@
#include "config.hpp" #include "config.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "contrib/transwarp.hpp" #include "contrib/transwarp.hpp"
#include "utils/output.hpp"
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
@ -76,16 +77,16 @@ 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; error << "Error: Server name already exists: " << server_name << std::endl;
std::cerr << "Current server path: " << server_existing_dir << 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; error << "Error: Local server definition path not found" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << 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,20 +94,20 @@ 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 + "/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_USER\": \"" << user << "\"," << std::endl;
server_env_file << std::endl; server_env_file << " \"SSH_PORT\": " << 22 << "," << std::endl;
server_env_file << "DROPSHELL_DIR=/home/"+user+"/.dropshell" << std::endl; server_env_file << " \"DROPSHELL_DIR\": \"" << "/home/"+user+"/.dropshell\"" << 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;
} }

View File

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

View File

@ -137,7 +137,7 @@ std::set<std::string> list_backups(const std::string &server_name, const std::st
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;

View File

@ -185,7 +185,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;
@ -252,7 +252,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)

View File

@ -41,6 +41,16 @@ namespace localfile {
return (servicepath.empty() ? "" : (fs::path(servicepath) / ".template_info.env").string()); return (servicepath.empty() ? "" : (fs::path(servicepath) / ".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
//------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------

View File

@ -14,10 +14,30 @@ namespace dropshell {
// ~/.config/dropshell/dropshell.json // ~/.config/dropshell/dropshell.json
// ~/.local/dropshell_agent // ~/.local/dropshell_agent
// |-- bb64 (only used locally, as it's for the local machine's architecture!) // |-- agent-local
// |-- files_for_remote_agent // |-- agent-install.sh
// |-- (other agent files, including _allservicesstatus.sh) // |-- bb64 (only used locally, as it's for the local machine's architecture!)
// |-- template_example
// |-- agent-remote
// |-- (remote agent files, including _allservicesstatus.sh)
// ~/.local/dropshell_files
// |-- backups
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// |-- temp_files
// |-- template_cache
// |-- templates
// | |-- <template_name>.json
// | |-- <template_name>
// | |-- (...script files...)
// | |-- _default.env
// | |-- config
// | |-- service.env
// | |-- .template_info.env
// | |-- (...other service config files...)
// |-- remote_versions
// | |-- server_name-service_name.json
// server_definition_path // server_definition_path
// |-- <server_name> // |-- <server_name>
// |-- server.json // |-- server.json
@ -27,23 +47,7 @@ namespace dropshell {
// |-- .template_info.env // |-- .template_info.env
// |-- (...other config files for specific server&service...) // |-- (...other config files for specific server&service...)
// backup path
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// temp files path
// template cache path
// |-- templates
// | |-- <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 +55,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 +65,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

View File

@ -29,7 +29,7 @@ namespace dropshell
{ {
if (command.get_command_to_run().empty()) if (command.get_command_to_run().empty())
return false; return false;
std::string full_command = command.construct_cmd(localpath::agent()+"/bb64"); // Get the command string std::string full_command = command.construct_cmd(localfile::bb64()); // Get the command string
pid_t pid = fork(); pid_t pid = fork();
@ -130,7 +130,7 @@ namespace dropshell
std::string full_cmd; std::string full_cmd;
if (!hasFlag(mode, cMode::NoBB64)) if (!hasFlag(mode, cMode::NoBB64))
full_cmd = command.construct_cmd(localpath::agent()+"/bb64"); full_cmd = command.construct_cmd(localfile::bb64());
else else
full_cmd = command.construct_cmd(""); full_cmd = command.construct_cmd("");

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

@ -7,6 +7,8 @@
#include <filesystem> #include <filesystem>
#include <regex> #include <regex>
#include <random> #include <random>
#include <sys/ioctl.h>
#include <unistd.h>
namespace dropshell { namespace dropshell {
@ -388,4 +390,46 @@ 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';
}
} // namespace dropshell } // namespace dropshell

View File

@ -55,4 +55,8 @@ 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);
} // namespace dropshell } // namespace dropshell