diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp deleted file mode 100644 index 539c3aa..0000000 --- a/src/autocomplete.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "dropshell.hpp" -#include "init_user_directory.hpp" -#include "config.hpp" -#include -#include -#include - -namespace fs = boost::filesystem; - -namespace dropshell { - -std::vector autocomplete_list_servers() { - std::vector servers; - std::string user_dir; - - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; - return servers; - } - - fs::path servers_dir = fs::path(user_dir) / "servers"; - if (!fs::exists(servers_dir)) { - std::cerr << "Error: Servers directory not found" << std::endl; - return servers; - } - - // List all server directories - for (const auto& entry : fs::directory_iterator(servers_dir)) { - if (fs::is_directory(entry)) { - servers.push_back(entry.path().filename().string()); - } - } - - return servers; -} - -std::vector autocomplete_list_services(const std::string& server_name) { - std::vector services; - std::string user_dir; - - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; - return services; - } - - fs::path server_dir = fs::path(user_dir) / "servers" / server_name; - if (!fs::exists(server_dir)) { - std::cerr << "Error: Server directory not found" << std::endl; - return services; - } - - // Look for .env files in the server directory - for (const auto& entry : fs::directory_iterator(server_dir)) { - if (entry.path().extension() == ".env" && entry.path().filename().string() != "_server.env") { - services.push_back(entry.path().stem().string()); - } - } - - return services; -} - -std::vector autocomplete_list_commands() { - std::vector commands; - std::set unique_commands; // To ensure deduplication - - - NEED TO CHANGE THIS SO IT ITERATES THROUGH ACTUAL INSTALLED SERVICES, NOT TEMPLATES! - - // Helper function to add commands from a directory - auto add_commands_from_dir = [&unique_commands](const std::string& dir_path) { - if (!fs::exists(dir_path)) { - return; - } - - // Iterate through all template directories - for (const auto& template_entry : fs::directory_iterator(dir_path)) { - if (!fs::is_directory(template_entry)) { - continue; - } - - // Look for shell files in each template directory - for (const auto& file_entry : fs::directory_iterator(template_entry.path())) { - if (fs::is_regular_file(file_entry) && - file_entry.path().extension() == ".sh" && - file_entry.path().filename().string()[0] != '_') { - unique_commands.insert(file_entry.path().stem().string()); - } - } - } - }; - - // System templates directory - const std::string system_templates_dir = "/opt/dropshell/templates"; - add_commands_from_dir(system_templates_dir); - - std::string user_templates_dir; - if (get_user_directory(user_templates_dir)) { - // User templates directory - user_templates_dir += "/usertemplates"; - add_commands_from_dir(user_templates_dir); - } - // Convert set to vector for return - commands.assign(unique_commands.begin(), unique_commands.end()); - return commands; -} - -} // namespace dropshell \ No newline at end of file diff --git a/src/autocomplete.hpp b/src/autocomplete.hpp deleted file mode 100644 index 591f6ab..0000000 --- a/src/autocomplete.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef __AUTOCOMPLETE_H -#define __AUTOCOMPLETE_H - -#include -#include - -namespace dropshell { - -std::vector autocomplete_list_servers(); -std::vector autocomplete_list_services(const std::string& server_name); - -} // namespace dropshell - -#endif // __AUTOCOMPLETE_H diff --git a/src/config.cpp b/src/config.cpp index 8b79874..62baa9f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,68 +1,87 @@ -#include "dropshell.hpp" -#include "init_user_directory.hpp" +#include "utils/directories.hpp" #include #include #include #include #include #include "config.hpp" +#include "envmanager.hpp" namespace fs = boost::filesystem; namespace pt = boost::property_tree; namespace dropshell { -// Default user directory -static bool config_loaded = false; -bool is_config_loaded() { - return config_loaded; +config *get_global_config() { + static config *gConfig = new config(); + return gConfig; } -bool get_config_path(std::string &path) -{ - // Try ~/.config/dropshell/dropshell.conf - const char* home = std::getenv("HOME"); - if (home) { - fs::path user_path = fs::path(home) / ".config" / "dropshell" / "dropshell.conf"; - path = user_path.string(); - return fs::exists(path); - } - std::cerr << "Warning: Couldn't determine user directory" << std::endl; - path = ""; - return false; +config::config() { +} +config::~config() { } -bool load_config() { - std::string config_path; - if (!get_config_path(config_path)) +bool config::load_config() { + std::string config_path = get_local_dropshell_config_path(); + if (config_path.empty()) return false; - try { - pt::ptree tree; - pt::read_ini(config_path, tree); - bool config_okay = false; - - // Try to read user directory from config - try { - std::string user_dir; - user_dir = tree.get("user.directory"); - // Update user directory through the new interface - set_user_directory(user_dir); - config_okay = true; - - } catch (const pt::ptree_error&) { - std::cerr << "Warning: User directory not set in config" << std::endl; + envmanager config_env(config_path); + if (!config_env.load()) + { + std::cerr << "Warning: Unable to read configuration file: " << config_path << std::endl; + return false; } - // config loaded okay. - config_loaded = config_okay; - return true; - - } catch (const std::exception& e) { - std::cerr << "Warning: Error reading config file: " << e.what() << std::endl; - return true; // Not a critical error - } + local_config_directory = config_env.get_variable_substituted("local.config.directory"); + if (local_config_directory.empty()) + { + std::cerr << "Warning: User directory not set in config" << std::endl; + return false; + } + return true; } +void config::save_config() +{ + std::string config_path = get_local_dropshell_config_path(); + if (config_path.empty()) + { + std::cerr << "Warning: Unable to save configuration file, as DropShell has not been initialised."<< std::endl; + std::cerr << "Please run 'dropshell init ' to initialise DropShell." << std::endl; + return; + } + + envmanager config_env(config_path); + config_env.set_variable("local.config.directory", local_config_directory); + config_env.save(); +} + +bool config::is_config_set() const +{ + return !local_config_directory.empty(); +} + +bool config::get_local_config_directory(std::string& path) const { + path = local_config_directory; + return !path.empty(); +} + +void config::init_local_config_directory(const std::string& path) { + // Convert to canonical path + fs::path abs_path = fs::canonical(path); + + // The directory must exist + if (!fs::exists(abs_path)) { + throw std::runtime_error("The specifieduser directory does not exist: " + abs_path.string()); + } + + local_config_directory = abs_path.string(); + save_config(); + std::cout << "Local config directory initialized to: " << abs_path.string() << std::endl; +} + + } // namespace dropshell \ No newline at end of file diff --git a/src/config.hpp b/src/config.hpp index 364d64a..e960aa0 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -4,9 +4,23 @@ namespace dropshell { -// Configuration functions -bool get_config_path(std::string& path); -bool load_config(); -bool is_config_loaded(); +class config { + public: + config(); + ~config(); + bool load_config(); + void save_config(); + + bool is_config_set() const; + + bool get_local_config_directory(std::string& path) const; + void init_local_config_directory(const std::string& path); + + private: + std::string local_config_directory; +}; + + +config *get_global_config(); } // namespace dropshell \ No newline at end of file diff --git a/src/dropshell.cpp b/src/dropshell.cpp deleted file mode 100644 index b664488..0000000 --- a/src/dropshell.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "dropshell.hpp" -#include "templates.hpp" -#include -#include - -void dropshell::list_templates() { - template_manager tm; - std::vector templates; - - if (!tm.get_templates(templates)) { - std::cerr << "Error: Failed to get templates" << std::endl; - return; - } - - if (templates.empty()) { - std::cout << "No templates found." << std::endl; - return; - } - - std::cout << "Available templates:" << std::endl; - std::cout << std::left << std::setw(20) << "Name" << "Path" << std::endl; - std::cout << std::string(60, '-') << std::endl; - - for (const auto& t : templates) { - std::cout << std::left << std::setw(20) << t.name << t.path << std::endl; - } -} \ No newline at end of file diff --git a/src/init_user_directory.cpp b/src/init_user_directory.cpp deleted file mode 100644 index 63f6ef3..0000000 --- a/src/init_user_directory.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "init_user_directory.hpp" -#include "config.hpp" -#include -#include -#include -#include -#include - -namespace fs = boost::filesystem; -namespace pt = boost::property_tree; - -namespace dropshell { - -static std::string user_directory; -bool get_user_directory(std::string& path) { - path = user_directory; - return !path.empty(); -} -void set_user_directory(const std::string& path) { - user_directory = path; -} - -void init_user_directory(const std::string& path) { - // Convert to canonical path - fs::path abs_path = fs::canonical(path); - - // The directory must exist - if (!fs::exists(abs_path)) { - throw std::runtime_error("The user directory does not exist: " + abs_path.string()); - } - - // create the servers subdirectory if it doesn't exist - fs::path servers_dir = abs_path / "servers"; - if (!fs::exists(servers_dir)) { - fs::create_directories(servers_dir); - } - - // Update config file - std::string config_path; - if (!get_config_path(config_path)) { - // No config file exists, create one in user's home directory - const char* home = std::getenv("HOME"); - if (!home) { - throw std::runtime_error("HOME environment variable not set"); - } - - fs::path config_dir = fs::path(home) / ".config" / "dropshell"; - if (!fs::exists(config_dir)) { - fs::create_directories(config_dir); - } - config_path = (config_dir / "dropshell.conf").string(); - } - - try { - pt::ptree tree; - // Read existing config if it exists - if (fs::exists(config_path)) { - pt::read_ini(config_path, tree); - } - - // Update user directory - tree.put("user.directory", abs_path.string()); - - // Write back to config file - pt::write_ini(config_path, tree); - - // Update in-memory value - user_directory = abs_path.string(); - - std::cout << "User directory initialized to: " << abs_path.string() << std::endl; - } catch (const std::exception& e) { - throw std::runtime_error("Failed to update config: " + std::string(e.what())); - } -} - -} // namespace dropshell \ No newline at end of file diff --git a/src/init_user_directory.hpp b/src/init_user_directory.hpp deleted file mode 100644 index 2eed7fb..0000000 --- a/src/init_user_directory.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -namespace dropshell { - -// User directory initialization function -void init_user_directory(const std::string& path); -bool get_user_directory(std::string& path); -void set_user_directory(const std::string& path); -} // namespace dropshell \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 46c6296..4ed121e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,9 @@ -#include "dropshell.hpp" -#include "server_service.hpp" -#include "autocomplete.hpp" -#include "init_user_directory.hpp" +#include "main.hpp" +#include "config.hpp" +#include "service_runner.hpp" +#include "services.hpp" +#include "servers.hpp" +#include "utils/directories.hpp" #include "config.hpp" #include @@ -28,28 +30,20 @@ void print_help() { std::cout << " backup SERVER [SERVICE] Backup service(s)." << std::endl; std::cout << " COMMAND SERVER [SERVICE] Run a custom command on service(s)." << std::endl; std::cout << std::endl; - std::cout << "Examples:" << std::endl; - std::cout << " dropshell servers" << std::endl; - std::cout << " dropshell servers myserver" << std::endl; - std::cout << " dropshell init /path/to/directory" << std::endl; - std::cout << " dropshell templates" << std::endl; - std::cout << " dropshell install myserver myservice" << std::endl; - std::cout << " dropshell run myserver myservice status" << std::endl; - std::cout << " dropshell backup myserver myservice" << std::endl; } } // namespace dropshell -bool parseargs(std::string arg2, std::string arg3, std::string & server_name, std::vector& servicelist) +bool parseargs(std::string arg2, std::string arg3, std::string & server_name, std::vector& servicelist) { if (arg2.empty()) return false; server_name = arg2; if (arg3.empty()) { - servicelist = dropshell::get_server_services(server_name); + servicelist = dropshell::get_server_services_info(server_name); } else { - servicelist.push_back(arg3); + servicelist.push_back(dropshell::get_service_info(server_name, arg3)); } return true; @@ -63,6 +57,7 @@ std::string safearg(int argc, char *argv[], int index) int main(int argc, char* argv[]) { try { + dropshell::config *cfg = dropshell::get_global_config(); // Handle commands std::string cmd; @@ -77,7 +72,7 @@ int main(int argc, char* argv[]) { return 1; } try { - dropshell::init_user_directory(argv[2]); + cfg->init_local_config_directory(argv[2]); return 0; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; @@ -96,27 +91,27 @@ int main(int argc, char* argv[]) { } // silently attempt to load the config file. - dropshell::load_config(); + cfg->load_config(); + // auto completion stuff. + std::set commands; + std::vector servers = dropshell::get_configured_servers(); + for (const auto& server : servers) + { + std::vector services = dropshell::get_server_services_info(server.name); + for (const auto& service : services) + commands.merge(dropshell::get_used_commands(server.name, service.service_name)); + } - // auto compeltion stuff. - auto commands = dropshell::autocomplete_list_commands(); if (cmd == "autocomplete_list_commands") { - // add in standard commands. - commands.insert(commands.end(), { - "help", - "version", - "init" + commands.merge(std::set{ + "help","version","init" }); - if (dropshell::is_config_loaded()) { // these only work if the config is loaded. - commands.insert(commands.end(), { - "servers", - "templates", - "install", - "backup" + if (cfg->is_config_set()) + commands.merge(std::set{ + "servers","templates","install","backup" }); - } - + for (const auto& command : commands) { std::cout << command << std::endl; } @@ -124,11 +119,11 @@ int main(int argc, char* argv[]) { } if (cmd == "autocomplete_list_servers") { - if (dropshell::is_config_loaded()) + if (cfg->is_config_set()) { - auto servers = dropshell::autocomplete_list_servers(); + auto servers = dropshell::get_configured_servers(); for (const auto& server : servers) - std::cout << server << std::endl; + std::cout << server.name << std::endl; } return 0; } @@ -138,17 +133,17 @@ int main(int argc, char* argv[]) { std::cerr << "Error: autocomplete_list_services requires a server name" << std::endl; return 1; } - if (dropshell::is_config_loaded()) { - auto services = dropshell::autocomplete_list_services(argv[2]); + if (cfg->is_config_set()) { + auto services = dropshell::get_server_services_info(argv[2]); for (const auto& service : services) - std::cout << service << std::endl; + std::cout << service.service_name << std::endl; } return 0; } // ------------------------------------------------------------ // from here we require the config file to be loaded. - if (!dropshell::is_config_loaded()) { + if (!cfg->is_config_set()) { std::cerr << "Error: Failed to load configuration." << std::endl << "Please run 'dropshell init ' to initialise the user directory and create a configuration file." << std::endl; return 1; } @@ -177,14 +172,14 @@ int main(int argc, char* argv[]) { if (cmd == "install") { std::string server_name; - std::vector servicelist; + std::vector servicelist; if (!parseargs(safearg(argc, argv, 2), safearg(argc, argv, 3), server_name, servicelist)) { std::cerr << "Error: install command requires server name and optionally service name" << std::endl; return 1; } - for (const auto& service_name : servicelist) { - dropshell::server_service service; - if (!service.init(server_name, service_name)) { + for (const auto& service_info : servicelist) { + dropshell::service_runner service; + if (!service.init(server_name, service_info.service_name)) { std::cerr << "Error: Failed to initialize service" << std::endl; return 1; } @@ -199,15 +194,15 @@ int main(int argc, char* argv[]) { if (cmd == "backup") { std::string server_name; - std::vector servicelist; + std::vector servicelist; if (!parseargs(safearg(argc, argv, 2), safearg(argc, argv, 3), server_name, servicelist)) { std::cerr << "Error: backup command requires server name and optionally service name" << std::endl; return 1; } - for (const auto& service_name : servicelist) { - dropshell::server_service service; - if (!service.init(server_name, service_name)) { + for (const auto& service_info : servicelist) { + dropshell::service_runner service; + if (!service.init(server_name, service_info.service_name)) { std::cerr << "Error: Failed to initialize service" << std::endl; return 1; } @@ -224,15 +219,15 @@ int main(int argc, char* argv[]) { for (const auto& command : commands) { if (cmd == command) { std::string server_name; - std::vector servicelist; + std::vector servicelist; if (!parseargs(safearg(argc, argv, 2), safearg(argc, argv, 3), server_name, servicelist)) { std::cerr << "Error: " << command << " command requires server name and optionally service name" << std::endl; return 1; } - for (const auto& service_name : servicelist) { - dropshell::server_service service; - if (!service.init(server_name, service_name)) { + for (const auto& service_info : servicelist) { + dropshell::service_runner service; + if (!service.init(server_name, service_info.service_name)) { std::cerr << "Error: Failed to initialize service" << std::endl; return 1; } diff --git a/src/dropshell.hpp b/src/main.hpp similarity index 58% rename from src/dropshell.hpp rename to src/main.hpp index ccc8837..e18463e 100644 --- a/src/dropshell.hpp +++ b/src/main.hpp @@ -13,13 +13,6 @@ const std::string RELEASE_DATE = "2025-04-21"; const std::string AUTHOR = "j842"; const std::string LICENSE = "MIT"; -// Server information structure -struct ServerInfo { - std::string name; - std::string ssh_host; - std::string ssh_user; - std::string ssh_port; -}; // Command handlers void print_help(const boost::program_options::options_description& desc); @@ -30,10 +23,4 @@ void list_templates(); void show_server_details(const std::string& server_name); void interactive_mode(); -// Utility functions -std::vector get_configured_servers(); -std::vector autocomplete_list_servers(); -std::vector autocomplete_list_services(const std::string& server_name); -std::vector autocomplete_list_commands(); - } // namespace dropshell \ No newline at end of file diff --git a/src/server_env.cpp b/src/server_env.cpp index 8305f60..cd100f7 100644 --- a/src/server_env.cpp +++ b/src/server_env.cpp @@ -1,121 +1,56 @@ #include "server_env.hpp" -#include -#include +#include "utils/envmanager.hpp" +#include "utils/directories.hpp" #include -#include #include +#include namespace dropshell { -// Helper function to trim whitespace from both ends of a string -static std::string trim(const std::string& str) { - const std::string whitespace = " \t"; - const auto strBegin = str.find_first_not_of(whitespace); - if (strBegin == std::string::npos) { - return ""; // empty string - } - const auto strEnd = str.find_last_not_of(whitespace); - const auto strRange = strEnd - strBegin + 1; - return str.substr(strBegin, strRange); -} - -// Helper function to print the contents of a file to screen -static void print_file(const std::string& path) { - std::cout << "Contents of " << path << ":" << std::endl; - std::ifstream file(path); - std::string line; - while (std::getline(file, line)) { - std::cout << " " << line << std::endl; - } -} - - -bool server_env::is_valid() { +bool server_env::is_valid() const { return mValid; } -server_env::server_env(const std::string& path) : mValid(false) { - // Construct the full path to _server.env - boost::filesystem::path env_path = boost::filesystem::path(path) / "_server.env"; +server_env::server_env(const std::string& server_name) : mValid(false) { + // Construct the full path to server.env + std::string env_path = get_local_server_env_path(server_name); // Check if file exists if (!boost::filesystem::exists(env_path)) { - throw std::runtime_error("Server environment file not found: " + env_path.string()); + throw std::runtime_error("Server environment file not found: " + env_path); } try { - // Read the environment file - std::ifstream file(env_path.string()); - std::string line; + // Use envmanager to handle the environment file + m_env_manager = std::unique_ptr(new envmanager(env_path)); + m_env_manager->load(); - while (std::getline(file, line)) { - // Skip empty lines and comments - if (line.empty() || line[0] == '#') { - continue; - } - - // Find the position of the equals sign - size_t pos = line.find('='); - if (pos != std::string::npos) { - std::string key = line.substr(0, pos); - std::string value = line.substr(pos + 1); - - // Trim whitespace using the helper function - key = trim(key); - value = trim(value); - - // Handle ${USER} replacement - size_t user_pos; - while ((user_pos = value.find("${USER}")) != std::string::npos) { - const char* user = std::getenv("USER"); - if (user) { - value.replace(user_pos, 7, user); - } - } - - variables[key] = value; - } - } + // Get all variables + m_env_manager->get_all_variables_substituted(variables); // Verify required variables exist for (const auto& var : {"SSH_HOST", "SSH_USER", "SSH_PORT", "DROPSHELL_DIR"}) { if (variables.find(var) == variables.end()) { - // print the contents of the _server.env file to screen: - print_file(env_path.string()); - // print variables identified in the file: + // Print the variables identified in the file std::cout << "Variables identified in the file:" << std::endl; - for (const auto& var : variables) { - std::cout << " " << var.first << std::endl; + for (const auto& v : variables) { + std::cout << " " << v.first << std::endl; } throw std::runtime_error("Missing required variable: " + std::string(var)); } } mValid = true; - } catch (const boost::property_tree::ini_parser_error& e) { + } catch (const std::exception& e) { throw std::runtime_error("Failed to parse server environment file: " + std::string(e.what())); } } -std::string server_env::get_variable(const std::string& name) { - auto it = variables.find(name); - if (it == variables.end()) { +std::string server_env::get_variable(const std::string& name) const { + if (!m_env_manager) { return ""; } - - std::string value = it->second; - - // Replace ${USER} with actual username - const char* username = std::getenv("USER"); - if (username) { - std::string user_var = "${USER}"; - size_t pos = value.find(user_var); - if (pos != std::string::npos) { - value.replace(pos, user_var.length(), username); - } - } - - return value; + return m_env_manager->get_variable_substituted(name); } const std::map &server_env::get_variables() const @@ -123,19 +58,19 @@ const std::map &server_env::get_variables() const return variables; } -std::string server_env::get_SSH_HOST() { +std::string server_env::get_SSH_HOST() const { return get_variable("SSH_HOST"); } -std::string server_env::get_SSH_USER() { +std::string server_env::get_SSH_USER() const { return get_variable("SSH_USER"); } -std::string server_env::get_SSH_PORT() { +std::string server_env::get_SSH_PORT() const { return get_variable("SSH_PORT"); } -std::string server_env::get_DROPSHELL_DIR() { +std::string server_env::get_DROPSHELL_DIR() const { return get_variable("DROPSHELL_DIR"); } diff --git a/src/server_env.hpp b/src/server_env.hpp index e79c2bb..e16a8bd 100644 --- a/src/server_env.hpp +++ b/src/server_env.hpp @@ -1,17 +1,17 @@ // server_env.hpp // -// read the _server.env file and provide a class to access the variables -// +// read the server.env file and provide a class to access the variables #ifndef __SERVER_ENV_HPP #define __SERVER_ENV_HPP #include #include - +#include +#include "utils/envmanager.hpp" namespace dropshell { -// reads path / _server.env and provides a class to access the variables. +// 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 @@ -20,21 +20,22 @@ namespace dropshell { // ${USER} -> the username of the user running dropshell class server_env { public: - server_env(const std::string& path); - std::string get_variable(const std::string& name); + server_env(const std::string& server_name); + std::string get_variable(const std::string& name) const; const std::map& get_variables() const; - std::string get_SSH_HOST(); - std::string get_SSH_USER(); - std::string get_SSH_PORT(); + std::string get_SSH_HOST() const; + std::string get_SSH_USER() const; + std::string get_SSH_PORT() const; - std::string get_DROPSHELL_DIR(); + std::string get_DROPSHELL_DIR() const; - bool is_valid(); + bool is_valid() const; private: std::map variables; bool mValid; + std::unique_ptr m_env_manager; }; } // namespace dropshell diff --git a/src/servers.cpp b/src/servers.cpp index f3bf153..d3dba60 100644 --- a/src/servers.cpp +++ b/src/servers.cpp @@ -1,9 +1,11 @@ -#include "init_user_directory.hpp" -#include "dropshell.hpp" +#include "servers.hpp" #include "server_env.hpp" -#include "server_service.hpp" +#include "service_runner.hpp" #include "tableprint.hpp" #include "interactive/interactive.hpp" +#include "utils/envmanager.hpp" +#include "utils/directories.hpp" +#include "services.hpp" #include #include #include @@ -16,32 +18,29 @@ namespace dropshell { std::vector get_configured_servers() { std::vector servers; - std::string user_dir; - if (!is_config_loaded()) { - std::cerr << "Error: Config not loaded" << std::endl; - return servers; - } - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; - return servers; - } - fs::path servers_dir = fs::path(user_dir) / "servers"; - if (!fs::exists(servers_dir)) { + std::string servers_dir = get_local_config_servers_path(); + if (servers_dir.empty()) { std::cerr << "Error: Servers directory not found" << std::endl; return servers; } + if (!fs::exists(servers_dir)) { + std::cerr << "Error: Servers directory not found:" << servers_dir << std::endl; + return servers; + } + for (const auto& entry : fs::directory_iterator(servers_dir)) { if (fs::is_directory(entry)) { - fs::path env_file = entry.path(); - server_env env(env_file.string()); + std::string server_name = entry.path().filename().string(); + + server_env env(server_name); if (!env.is_valid()) { - std::cerr << "Error: Invalid server environment file: " << env_file.string() << std::endl; + std::cerr << "Error: Invalid server environment file: " << server_name << std::endl; continue; } servers.push_back({ - entry.path().filename().string(), + server_name, env.get_SSH_HOST(), env.get_SSH_USER(), env.get_SSH_PORT() @@ -91,10 +90,10 @@ void list_servers() { for (const auto& server : servers) { std::vector ports_used; std::string serviceticks = ""; - std::vector services = get_server_services(server.name); + std::vector services = get_server_services_info(server.name); for (const auto& service : services) { - server_service ss; - if (ss.init(server.name, service)) + service_runner ss; + if (ss.init(server.name, service.service_name)) { if (ss.is_healthy()) serviceticks += ":tick: "; @@ -120,21 +119,12 @@ void list_servers() { } void show_server_details(const std::string& server_name) { - std::string user_dir; - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; + server_env env(server_name); + if (!env.is_valid()) { + std::cerr << "Error: Invalid server environment file: " << server_name << std::endl; return; } - fs::path server_dir = fs::path(user_dir) / "servers" / server_name; - if (!fs::exists(server_dir)) { - std::cerr << "Error: Server '" << server_name << "' not found" << std::endl; - return; - } - - server_env env(server_dir.string()); - - //--------------------- // Check if server is reachable via SSH std::string ssh_address = env.get_SSH_HOST(); @@ -176,12 +166,12 @@ void show_server_details(const std::string& server_name) { tableprint tp("Services: " + server_name, false); tp.add_row({"Status", "Service", "Ports"}); - std::vector services = get_server_services(server_name); + std::vector services = get_server_services_info(server_name); for (const auto& service : services) { bool healthy = false; std::vector ports; - server_service ss; - if (ss.init(server_name, service)) + service_runner ss; + if (ss.init(server_name, service.service_name)) { if (ss.is_healthy()) healthy=true; @@ -196,7 +186,7 @@ void show_server_details(const std::string& server_name) { ports_str += std::to_string(port); first = false; } - tp.add_row({healthy ? ":tick:" : ":cross:", service, ports_str}); + tp.add_row({healthy ? ":tick:" : ":cross:", service.service_name, ports_str}); } // end of for (const auto& service : services) tp.print(); } // end of list services diff --git a/src/servers.hpp b/src/servers.hpp new file mode 100644 index 0000000..b7f6cc0 --- /dev/null +++ b/src/servers.hpp @@ -0,0 +1,22 @@ +#ifndef SERVERS_HPP +#define SERVERS_HPP + +#include +#include + +namespace dropshell { + +// Server information structure +struct ServerInfo { + std::string name; + std::string ssh_host; + std::string ssh_user; + std::string ssh_port; +}; + +std::vector get_configured_servers(); + + +} // namespace dropshell + +#endif // SERVERS_HPP diff --git a/src/server_service.cpp b/src/service_runner.cpp similarity index 53% rename from src/server_service.cpp rename to src/service_runner.cpp index d37c69e..67e5bb4 100644 --- a/src/server_service.cpp +++ b/src/service_runner.cpp @@ -1,9 +1,10 @@ #include "config.hpp" -#include "init_user_directory.hpp" -#include "server_service.hpp" +#include "service_runner.hpp" #include "server_env.hpp" #include "templates.hpp" #include "config.hpp" +#include "services.hpp" +#include "utils/directories.hpp" #include #include #include @@ -16,57 +17,13 @@ namespace fs = boost::filesystem; namespace dropshell { -std::vector get_server_services(const std::string& server_name) { - std::vector services; - std::string user_dir; - - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; - return services; - } - - fs::path server_dir = fs::path(user_dir) / "servers" / server_name; - if (!fs::exists(server_dir)) { - std::cerr << "Error: Server directory not found" << std::endl; - return services; - } - - // Look for .env files in the server directory - for (const auto& entry : fs::directory_iterator(server_dir)) { - if (entry.path().extension() == ".env" && entry.path().filename().string() != "_server.env") { - services.push_back(entry.path().stem().string()); - } - } - - return services; -} -server_service::server_service() : m_server_name(""), m_service_name(""), m_server_env(nullptr) {} +service_runner::service_runner() : m_server_name(""), m_server_env(nullptr) {} -bool server_service::init(const std::string& server_name, const std::string& service_name) { - std::string user_dir; - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; - return false; - } - - // Check if server exists - fs::path server_dir = fs::path(user_dir) / "servers" / server_name; - if (!fs::exists(server_dir)) { - std::cerr << "Error: Server '" << server_name << "' not found" << std::endl; - return false; - } - - // Check if service env file exists - fs::path service_env = server_dir / (service_name + ".env"); - if (!fs::exists(service_env)) { - std::cerr << "Error: Service environment file not found: " << service_env.string() << std::endl; - return false; - } - +bool service_runner::init(const std::string& server_name, const std::string& service_name) { // Initialize server environment try { - m_server_env = std::make_unique(server_dir.string()); + m_server_env = std::make_unique(server_name); if (!m_server_env->is_valid()) { std::cerr << "Error: Invalid server environment" << std::endl; return false; @@ -75,55 +32,49 @@ bool server_service::init(const std::string& server_name, const std::string& ser std::cerr << "Error: Failed to initialize server environment: " << e.what() << std::endl; return false; } - + + mRemote_service_path = get_remote_service_path(m_server_name, m_service_info.service_name); + mRemote_service_config_path = get_remote_service_config_path(m_server_name, m_service_info.service_name); + mRemote_service_template_path = get_remote_service_template_path(m_server_name, m_service_info.service_name); + mRemote_service_env_file = get_remote_service_env_file(m_server_name, m_service_info.service_name); + + m_server_name = server_name; - m_service_name = service_name; - return true; + m_service_info = get_service_info(server_name, service_name); + return !m_service_info.path.empty(); } // Helper method implementations -std::string server_service::construct_ssh_cmd() const { +std::string service_runner::construct_ssh_cmd() const { std::stringstream ssh_cmd; ssh_cmd << "ssh -p " << m_server_env->get_SSH_PORT() << " " << m_server_env->get_SSH_USER() << "@" << m_server_env->get_SSH_HOST() << " "; return ssh_cmd.str(); } -std::string server_service::get_service_dir() const { - return m_server_env->get_DROPSHELL_DIR() + "/" + m_service_name; -} - -std::string server_service::get_env_path() const { - return get_service_dir() + "/" + m_service_name + ".env"; -} - -std::string server_service::get_script_dir() const { - return get_service_dir() + "/template"; -} - -bool server_service::check_service_dir_exists(const std::string& ssh_cmd) const { - std::string check_dir_cmd = ssh_cmd + "'test -d " + get_service_dir() + "'"; +bool service_runner::check_remote_dir_exists(const std::string& ssh_cmd, const std::string& dir_path) const { + std::string check_dir_cmd = ssh_cmd + "'test -d " + dir_path + "'"; if (system(check_dir_cmd.c_str()) != 0) { - std::cerr << "Error: Service directory not found on server - has it been installed?" << std::endl; + std::cerr << "Error: Directory not found on remote server:" << dir_path << std::endl; return false; } return true; } -bool server_service::check_remote_file_exists(const std::string& ssh_cmd, const std::string& file_path) const { +bool service_runner::check_remote_file_exists(const std::string& ssh_cmd, const std::string& file_path) const { std::string check_cmd = ssh_cmd + "'test -f " + file_path + "'"; if (system(check_cmd.c_str()) != 0) { - std::cerr << "Error: File not found: " << file_path << std::endl; + std::cerr << "Error: File not found on remote server: " << file_path << std::endl; return false; } return true; } -bool server_service::execute_ssh_command(const std::string& command, const std::string& error_msg) const { +bool service_runner::execute_ssh_command(const std::string& command, const std::string& error_msg) const { std::string full_cmd = construct_ssh_cmd() + command; return execute_local_command(full_cmd, error_msg); } -bool server_service::execute_local_command(const std::string& command, const std::string& error_msg) const { +bool service_runner::execute_local_command(const std::string& command, const std::string& error_msg) const { bool okay = (system(command.c_str()) == 0); if (!okay && !error_msg.empty()) @@ -131,14 +82,14 @@ bool server_service::execute_local_command(const std::string& command, const std return okay; } -void server_service::maketitle(const std::string& title) const { +void service_runner::maketitle(const std::string& title) const { std::cout << std::string(title.length() + 4, '-') << std::endl; std::cout << "| " << title << " |" << std::endl; std::cout << std::string(title.length() + 4, '-') << std::endl; } -bool server_service::install() { - maketitle("Installing " + m_service_name + " on " + m_server_name); +bool service_runner::install() { + maketitle("Installing " + m_service_info.service_name + " (" + m_service_info.template_name + ") on " + m_server_name); if (!m_server_env) { std::cerr << "Error: Server service not initialized" << std::endl; return false; @@ -146,16 +97,14 @@ bool server_service::install() { // Check if template exists template_manager tm; - template_info info; - if (!tm.get_template_info(m_service_name, info)) { - std::cerr << "Error: Template '" << m_service_name << "' not found" << std::endl; + template_info tinfo; + if (!tm.get_template_info(m_service_info.template_name, tinfo)) { + std::cerr << "Error: Template '" << m_service_info.template_name << "' not found" << std::endl; return false; } - - std::string script_dir = get_script_dir(); - + // Create service directory - std::string mkdir_cmd = "'mkdir -p " + script_dir + "'"; + std::string mkdir_cmd = "'mkdir -p " + mRemote_service_path + "'"; if (!execute_ssh_command(mkdir_cmd, "Failed to create service directory")) { return false; } @@ -167,52 +116,55 @@ bool server_service::install() { } // Copy template files - std::cout << "Copying template files from " << info.path << " to " << script_dir << "/" << std::endl; - std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " + - info.path + "/ " + - m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + - script_dir + "/"; - execute_local_command(rsync_cmd,"Failed to copy template files"); - - // Copy service env file - std::string user_dir; - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; - return false; - } - fs::path service_env = fs::path(user_dir) / "servers" / m_server_name / (m_service_name + ".env"); - std::string scp_cmd = "scp -P " + m_server_env->get_SSH_PORT() + " " + - service_env.string() + " " + - m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + - get_env_path(); - if (!execute_ssh_command(scp_cmd, "Failed to copy service environment file")) { - return false; + { + std::cout << "Copying template files from " << tinfo.path << " to " << mRemote_service_template_path << "/" << std::endl; + std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " + + tinfo.path + "/ " + + m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + + mRemote_service_template_path + "/"; + execute_local_command(rsync_cmd,"Failed to copy template files"); } + // Copy service files (including service.env) + { + std::string local_service_path = get_local_service_path(m_server_name, m_service_info.service_name); + if (local_service_path.empty() || !fs::exists(local_service_path)) { + std::cerr << "Error: Service directory not found: " << local_service_path << std::endl; + return false; + } + std::cout << "Copying service files from " << local_service_path << " to " << mRemote_service_config_path << std::endl; + std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " + + local_service_path + "/ " + + m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" + + mRemote_service_config_path + "/"; + execute_local_command(rsync_cmd,"Failed to copy service files"); + } + // Run install script - std::string install_cmd = "'cd " + script_dir + - " && /bin/bash _install.sh " + get_env_path() + "'"; - bool ok= execute_ssh_command(install_cmd, "Failed to run install script"); - if (!ok) - return false; + { + std::string install_cmd = "'cd " + mRemote_service_template_path + + " && /bin/bash _install.sh " + mRemote_service_env_file + "'"; + bool ok= execute_ssh_command(install_cmd, "Failed to run install script"); + if (!ok) + return false; + } // print health tick std::cout << "Health: " << healthtick() << std::endl; return true; } -bool server_service::run_command(const std::string& command) { +bool service_runner::run_command(const std::string& command) { if (!m_server_env) { std::cerr << "Error: Server service not initialized" << std::endl; return false; } std::string ssh_cmd = construct_ssh_cmd(); - std::string script_dir = get_script_dir(); - std::string script_path = script_dir + "/" + command + ".sh"; + std::string script_path = mRemote_service_template_path + "/" + command + ".sh"; // Check if service directory exists - if (!check_service_dir_exists(ssh_cmd)) { + if (!check_remote_dir_exists(ssh_cmd, mRemote_service_path)) { return false; } @@ -222,18 +174,18 @@ bool server_service::run_command(const std::string& command) { } // Check if env file exists - if (!check_remote_file_exists(ssh_cmd, get_env_path())) { + if (!check_remote_file_exists(ssh_cmd, mRemote_service_env_file)) { return false; } // Run the command - std::string run_cmd = "'cd " + script_dir + - " && /bin/bash " + script_path + " " + get_env_path() + "'"; + std::string run_cmd = "'cd " + mRemote_service_template_path + + " && /bin/bash " + script_path + " " + mRemote_service_env_file + "'"; return execute_ssh_command(run_cmd, "Command returned error code: " + script_path); } -bool server_service::backup() { - maketitle("Backing up " + m_service_name + " on " + m_server_name); +bool service_runner::backup() { + maketitle("Backing up " + m_service_info.service_name + " (" + m_service_info.template_name + ") on " + m_server_name); if (!m_server_env) { std::cerr << "Error: Server service not initialized" << std::endl; @@ -241,11 +193,10 @@ bool server_service::backup() { } std::string ssh_cmd = construct_ssh_cmd(); - std::string script_dir = get_script_dir(); - std::string script_path = script_dir + "/_backup.sh"; + std::string script_path = mRemote_service_template_path + "/_backup.sh"; // Check if basic installed stuff is in place. - if (!check_service_dir_exists(ssh_cmd) || !check_remote_file_exists(ssh_cmd, script_path) || !check_remote_file_exists(ssh_cmd, get_env_path())) + if (!check_remote_dir_exists(ssh_cmd, mRemote_service_path) || !check_remote_file_exists(ssh_cmd, script_path) || !check_remote_file_exists(ssh_cmd, mRemote_service_env_file)) return false; // Create backups directory on server if it doesn't exist @@ -256,12 +207,7 @@ bool server_service::backup() { } // Create backups directory locally if it doesn't exist - std::string user_dir; - if (!get_user_directory(user_dir)) { - std::cerr << "Error: User directory not set" << std::endl; - return false; - } - fs::path local_backups_dir = fs::path(user_dir) / "backups"; + std::string local_backups_dir = get_local_config_backups_path(); if (!fs::exists(local_backups_dir)) { fs::create_directories(local_backups_dir); } @@ -273,13 +219,13 @@ bool server_service::backup() { datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S"); // Construct backup filename - std::string backup_filename = m_server_name + "-" + m_service_name + "-" + datetime.str() + ".tgz"; + std::string backup_filename = m_server_name + "-" + m_service_info.service_name + "-" + datetime.str() + ".tgz"; std::string server_backup_path = server_backups_dir + "/" + backup_filename; - std::string local_backup_path = (local_backups_dir / backup_filename).string(); + std::string local_backup_path = (fs::path(local_backups_dir) / backup_filename).string(); // Run backup script - std::string backup_cmd = "'cd " + script_dir + - " && /bin/bash \""+script_path+"\" " + get_env_path() + " " + server_backup_path + "'"; + std::string backup_cmd = "'cd " + mRemote_service_template_path + + " && /bin/bash \""+script_path+"\" " + mRemote_service_env_file + " " + server_backup_path + "'"; if (!execute_ssh_command(backup_cmd, "Backup script failed")) { return false; } @@ -296,22 +242,18 @@ bool server_service::backup() { return true; } -bool server_service::is_healthy() +bool service_runner::is_healthy() { if (!m_server_env) { std::cerr << "Error: Server service not initialized" << std::endl; return false; } - std::string service_dir = m_server_env->get_DROPSHELL_DIR() + "/" + m_service_name; - std::string script_dir = service_dir + "/template"; - std::string env_path = service_dir + "/" + m_service_name + ".env"; - // Run status script, does not display output. - return execute_ssh_command("'cd " + script_dir + " && /bin/bash _status.sh " + env_path + " > /dev/null 2>&1'",""); + return execute_ssh_command("'cd " + mRemote_service_template_path + " && /bin/bash _status.sh " + mRemote_service_env_file + " > /dev/null 2>&1'",""); } -std::string server_service::healthtick() +std::string service_runner::healthtick() { std::string green_tick = "\033[32m✓\033[0m"; std::string red_cross = "\033[31m✗\033[0m"; @@ -322,7 +264,7 @@ std::string server_service::healthtick() return red_cross; } -std::vector server_service::get_ports() +std::vector service_runner::get_ports() { std::vector ports; if (!m_server_env) { @@ -331,22 +273,16 @@ std::vector server_service::get_ports() } std::string ssh_cmd = construct_ssh_cmd(); - std::string script_dir = get_script_dir(); - std::string script_path = script_dir + "/_ports.sh"; - - // Check if service directory exists - if (!check_service_dir_exists(ssh_cmd)) { - return ports; - } - + std::string script_path = mRemote_service_template_path + "/_ports.sh"; + // Check if ports script exists if (!check_remote_file_exists(ssh_cmd, script_path)) { return ports; } // Run the ports script and capture output - std::string run_cmd = "'cd " + script_dir + - " && /bin/bash " + script_path + " " + get_env_path() + "'"; + std::string run_cmd = "'cd " + mRemote_service_template_path + + " && /bin/bash " + script_path + " " + mRemote_service_env_file + "'"; // Create a temporary file to store the output std::string temp_file = "/tmp/dropshell_ports_" + std::to_string(getpid()); diff --git a/src/server_service.hpp b/src/service_runner.hpp similarity index 83% rename from src/server_service.hpp rename to src/service_runner.hpp index 3266764..632d15f 100644 --- a/src/server_service.hpp +++ b/src/service_runner.hpp @@ -7,17 +7,16 @@ #include #include #include "server_env.hpp" +#include "services.hpp" namespace dropshell { -std::vector get_server_services(const std::string& server_name); - -class server_service { +class service_runner { public: - server_service(); + service_runner(); bool init(const std::string& server_name, const std::string& service_name); - // install the service over ssh, using the credentials from _server.env (via server_env.hpp), by: + // install the service over ssh, using the credentials from server.env (via server_env.hpp), by: // 1. check if the server_name exists, and the service_name refers to a valid template // 2. check if service_name is valid for the server_name // 3. create the service directory on the server at {DROPSHELL_DIR}/{service_name} @@ -26,7 +25,7 @@ class server_service { // 5. running the install.sh script on the server, passing the {service_name}.env file as an argument bool install(); - // 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 // then run the command, passing the {service_name}.env file as an argument // do a lot of checks, such as: @@ -37,7 +36,7 @@ class server_service { // checking that the {service_name}.env file exists in the service directory. bool run_command(const std::string& command); - // 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 // 2. create a backup file with format server-service-datetime.tgz // 3. store it in the server's DROPSHELL_DIR/backups folder @@ -59,15 +58,17 @@ class server_service { private: std::string m_server_name; - std::string m_service_name; + ServiceInfo m_service_info; std::unique_ptr m_server_env; + std::string mRemote_service_path; + std::string mRemote_service_config_path; + std::string mRemote_service_template_path; + std::string mRemote_service_env_file; + // Helper methods std::string construct_ssh_cmd() const; - std::string get_service_dir() const; - std::string get_env_path() const; - std::string get_script_dir() const; - bool check_service_dir_exists(const std::string& ssh_cmd) const; + bool check_remote_dir_exists(const std::string& ssh_cmd, const std::string& dir_path) const; bool check_remote_file_exists(const std::string& ssh_cmd, const std::string& file_path) const; bool execute_ssh_command(const std::string& command, const std::string& error_msg) const; bool execute_local_command(const std::string& command, const std::string& error_msg) const; diff --git a/src/services.cpp b/src/services.cpp new file mode 100644 index 0000000..68294dd --- /dev/null +++ b/src/services.cpp @@ -0,0 +1,93 @@ +#include "services.hpp" +#include "utils/envmanager.hpp" +#include "utils/directories.hpp" +#include "templates.hpp" +#include +#include + +namespace fs = boost::filesystem; + +namespace dropshell { + +std::vector get_server_services_info(const std::string& server_name) { + std::vector services; + + std::string serverpath = get_local_config_servers_path(); + if (serverpath.empty()) { + std::cerr << "Error: Server directory not found: " << serverpath << std::endl; + return services; + } + fs::path server_dir = fs::path(serverpath) / server_name; + if (!fs::exists(server_dir)) { + std::cerr << "Error: Server directory not found:" << server_dir.string() << std::endl; + return services; + } + for (const auto& entry : fs::directory_iterator(server_dir)) { + if (fs::is_directory(entry)) { + ServiceInfo service = get_service_info(server_name, entry.path().filename().string()); + if (!service.template_name.empty()) { + services.push_back(service); + } + } + } + + return services; +} + + +ServiceInfo get_service_info(const std::string &server_name, const std::string &service_name) +{ + std::string service_dir = get_local_service_path(server_name, service_name); + if (service_dir.empty()) { + std::cerr << "Error: Service directory not found: " << service_dir << std::endl; + return ServiceInfo(); + } + + ServiceInfo service; + std::string local_service_env_path = get_local_service_env_path(server_name, service_name); + envmanager env(local_service_env_path); + if (!env.load()) { + std::cerr << "Error: service.env missing from " << local_service_env_path << std::endl; + return ServiceInfo(); + } + service.template_name = env.get_variable("TEMPLATE"); + + if (service.template_name.empty()) { + std::cerr << "Error: TEMPLATE variable not defined in " << local_service_env_path << std::endl; + return ServiceInfo(); + } + + template_info tinfo; + template_manager tm; + if (!tm.get_template_info(service.template_name, tinfo)) { + std::cerr << "Error: Template '" << service.template_name << "' not found" << std::endl; + return ServiceInfo(); + } + + // find the template path + service.template_path = tinfo.path; + + return service; +} + +std::set get_used_commands(const std::string &server_name, const std::string &service_name) +{ + std::set commands; + + ServiceInfo service = get_service_info(server_name, service_name); + if (service.template_path.empty()) { + std::cerr << "Error: Service not found: " << service_name << std::endl; + return commands; + } + + // iterate over all files in the template path, and add the command name to the set. + // commands are .sh files that don't begin with _ + fs::path template_path = fs::path(service.template_path); + for (const auto& entry : fs::directory_iterator(template_path)) { + if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0)) + commands.insert(entry.path().filename().string()); + } + return commands; +} + +} // namespace dropshell diff --git a/src/services.hpp b/src/services.hpp new file mode 100644 index 0000000..9f24bdb --- /dev/null +++ b/src/services.hpp @@ -0,0 +1,24 @@ +#ifndef SERVICES_HPP +#define SERVICES_HPP + +#include +#include +#include + +namespace dropshell { + + struct ServiceInfo { + std::string path; + std::string service_name; + std::string template_name; + std::string template_path; + }; + + std::vector get_server_services_info(const std::string& server_name); + ServiceInfo get_service_info(const std::string& server_name, const std::string& service_name); + std::set get_used_commands(const std::string& server_name, const std::string& service_name); +} // namespace dropshell + + + +#endif diff --git a/src/templates.cpp b/src/templates.cpp index ccf0243..c63c2e4 100644 --- a/src/templates.cpp +++ b/src/templates.cpp @@ -1,10 +1,13 @@ #include "init_user_directory.hpp" #include "templates.hpp" #include "config.hpp" +#include "utils/directories.hpp" #include +#include #include #include #include +#include namespace dropshell { @@ -20,7 +23,7 @@ bool template_manager::get_templates(std::vector& templates) { templates.clear(); // System templates directory - const std::string system_templates_dir = "/opt/dropshell/templates"; + const std::string system_templates_dir = get_local_system_templates_path(); // User templates directory (from config) std::string user_templates_dir; if (!get_user_directory(user_templates_dir)) { @@ -77,4 +80,29 @@ bool template_manager::get_template_info(const std::string& name, template_info& return false; } + + +void list_templates() { + template_manager tm; + std::vector templates; + + if (!tm.get_templates(templates)) { + std::cerr << "Error: Failed to get templates" << std::endl; + return; + } + + if (templates.empty()) { + std::cout << "No templates found." << std::endl; + return; + } + + std::cout << "Available templates:" << std::endl; + std::cout << std::left << std::setw(20) << "Name" << "Path" << std::endl; + std::cout << std::string(60, '-') << std::endl; + + for (const auto& t : templates) { + std::cout << std::left << std::setw(20) << t.name << t.path << std::endl; + } +} + } // namespace dropshell \ No newline at end of file diff --git a/src/utils/directories.cpp b/src/utils/directories.cpp new file mode 100644 index 0000000..dc97562 --- /dev/null +++ b/src/utils/directories.cpp @@ -0,0 +1,160 @@ +#include "directories.hpp" +#include "config.hpp" +#include "server_env.hpp" +#include +#include +#include + +namespace fs = boost::filesystem; + +namespace dropshell { + + + +std::string get_local_dropshell_config_path() +{ + // Try ~/.config/dropshell/dropshell.conf + const char* home = std::getenv("HOME"); + if (home) { + fs::path user_path = fs::path(home) / ".config" / "dropshell" / "dropshell.env"; + return user_path.string(); + } + std::cerr << "Warning: Couldn't determine user directory" << std::endl; + return std::string(); +} + +std::string get_local_system_templates_path() +{ + return "/opt/dropshell/templates"; +} + +std::string get_local_config_path() +{ + config *cfg = get_global_config(); + std::string user_dir; + if (!cfg->get_local_config_directory(user_dir)) { + return std::string(); + } + return user_dir; +} + +std::string get_local_config_templates_path() +{ + std::string config_path = get_local_config_path(); + if (config_path.empty()) { + return std::string(); + } + return config_path + "/templates"; +} + +std::string get_local_config_servers_path() +{ + std::string config_path = get_local_config_path(); + if (config_path.empty()) { + return std::string(); + } + return config_path + "/servers"; +} + +std::string get_local_config_backups_path() +{ + std::string config_path = get_local_config_path(); + if (config_path.empty()) { + return std::string(); + } + return config_path + "/backups"; +} + +std::string get_local_server_path(const std::string &server_name) +{ + std::string config_path = get_local_config_path(); + if (config_path.empty()) + return std::string(); + return config_path + "/servers/" + server_name; +} + +std::string get_local_server_env_path(const std::string &server_name) +{ + std::string serverpath = get_local_server_path(server_name); + if (serverpath.empty()) + return std::string(); + return (fs::path(serverpath) / "server.env").string(); +} + +std::string get_local_service_path(const std::string &server_name, const std::string &service_name) +{ + std::string serverpath = get_local_server_path(server_name); + if (serverpath.empty()) + return std::string(); + return (fs::path(serverpath) / service_name).string(); +} + +std::string get_local_service_env_path(const std::string &server_name, const std::string &service_name) +{ + std::string servicepath = get_local_service_path(server_name, service_name); + if (servicepath.empty()) + return std::string(); + return (fs::path(servicepath) / "service.env").string(); +} + +// ------------------------------------------------------------------------------------------ + + // remote paths + // DROPSHELL_DIR + // |-- service name + // |-- config + // |-- service.env + // |-- (user config files) + // |-- template + // |-- (script files) + // |-- backups + + std::string get_remote_DROPSHELL_path(const std::string &server_name) + { + server_env env(server_name); + if (!env.is_valid()) + return std::string(); + return env.get_DROPSHELL_DIR(); + } + + std::string get_remote_service_path(const std::string &server_name, const std::string &service_name) + { + std::string dropshell_path = get_remote_DROPSHELL_path(server_name); + if (dropshell_path.empty()) + return std::string(); + return (fs::path(dropshell_path) / service_name).string(); + } + + std::string get_remote_service_config_path(const std::string &server_name, const std::string &service_name) + { + std::string service_path = get_remote_service_path(server_name, service_name); + if (service_path.empty()) + return std::string(); + return (fs::path(service_path) / "config").string(); + } + + std::string get_remote_service_template_path(const std::string &server_name, const std::string &service_name) + { + std::string service_path = get_remote_service_path(server_name, service_name); + if (service_path.empty()) + return std::string(); + return (fs::path(service_path) / "template").string(); + } + + std::string get_remote_service_backups_path(const std::string &server_name, const std::string &service_name) + { + std::string service_path = get_remote_service_path(server_name, service_name); + if (service_path.empty()) + return std::string(); + return (fs::path(service_path) / "backups").string(); + } + + std::string get_remote_service_env_file(const std::string &server_name, const std::string &service_name) + { + std::string service_path = get_remote_service_config_path(server_name, service_name); + if (service_path.empty()) + return std::string(); + return (fs::path(service_path) / "service.env").string(); + } + +} // namespace dropshell \ No newline at end of file diff --git a/src/utils/directories.hpp b/src/utils/directories.hpp new file mode 100644 index 0000000..7c55ff2 --- /dev/null +++ b/src/utils/directories.hpp @@ -0,0 +1,40 @@ +#ifndef DIRECTORIES_HPP +#define DIRECTORIES_HPP + +#include + +namespace dropshell { + + // local paths - return empty string on failure + std::string get_local_dropshell_config_path(); + std::string get_local_system_templates_path(); + std::string get_local_config_path(); + std::string get_local_config_templates_path(); + std::string get_local_config_servers_path(); + std::string get_local_config_backups_path(); + + std::string get_local_server_path(const std::string &server_name); + std::string get_local_server_env_path(const std::string &server_name); + + std::string get_local_service_path(const std::string &server_name, const std::string &service_name); + std::string get_local_service_env_path(const std::string &server_name, const std::string &service_name); + + // remote paths + // DROPSHELL_DIR + // |-- service name + // |-- config + // |-- service.env + // |-- (user config files) + // |-- template + // |-- (script files) + // |-- backups + std::string get_remote_DROPSHELL_path(const std::string &server_name); + std::string get_remote_service_path(const std::string &server_name, const std::string &service_name); + std::string get_remote_service_config_path(const std::string &server_name, const std::string &service_name); + std::string get_remote_service_template_path(const std::string &server_name, const std::string &service_name); + std::string get_remote_service_backups_path(const std::string &server_name, const std::string &service_name); + + std::string get_remote_service_env_file(const std::string &server_name, const std::string &service_name); +} // namespace dropshell + +#endif diff --git a/src/utils/envmanager.cpp b/src/utils/envmanager.cpp new file mode 100644 index 0000000..1624d4d --- /dev/null +++ b/src/utils/envmanager.cpp @@ -0,0 +1,135 @@ +#include "envmanager.hpp" +#include +#include +#include +#include +#include +#include // For std::getenv + +namespace dropshell { + +envmanager::envmanager(std::string path) : m_path(path) { +} + +envmanager::~envmanager() { +} + +bool envmanager::load() { + std::ifstream file(m_path); + if (!file.is_open()) { + return false; + } + + m_variables.clear(); + std::string line; + while (std::getline(file, line)) { + line=trim(line); + // Skip empty lines and comments + if (line.empty() || line[0] == '#') { + continue; + } + + size_t pos = line.find('='); + if (pos != std::string::npos) { + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + + // trim whitespace from the key and value + m_variables[trim(key)] = trim(value); + } + } + file.close(); + return true; +} + +void envmanager::save() { + std::ofstream file(m_path); + if (!file.is_open()) { + return; + } + + for (const auto& pair : m_variables) { + file << pair.first << "=" << pair.second << std::endl; + } + file.close(); +} + +std::string envmanager::get_variable(std::string key) const { + key = trim(key); + + // Use case-insensitive comparison to find the key + for (const auto& pair : m_variables) { + if (pair.first == key) { + return pair.second; + } + } + + return ""; +} + +void envmanager::get_all_variables(std::map& variables) const { + variables = m_variables; +} + +std::string envmanager::get_variable_substituted(std::string key) const { + std::string value = get_variable(key); + return expand_patterns(value); +} + +void envmanager::get_all_variables_substituted(std::map& variables) const { + variables.clear(); + for (const auto& pair : m_variables) { + variables[pair.first] = expand_patterns(pair.second); + } +} + +void envmanager::add_variables(std::map variables) { + for (auto& pair : variables) { + set_variable(pair.first, pair.second); + } +} + +void envmanager::set_variable(std::string key, std::string value) { + m_variables[trim(key)] = trim(value); +} + +void envmanager::clear_variables() { + m_variables.clear(); +} + +std::string envmanager::trim(std::string str) const { + // Trim leading whitespace + str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); + + // Trim trailing whitespace + str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), str.end()); + + return str; +} + +std::string envmanager::expand_patterns(std::string str) const { + // Combined regex pattern for both ${var} and $var formats + std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))"); + std::string result = str; + std::smatch match; + + while (std::regex_search(result, match, var_pattern)) { + // match[1] will contain capture from ${var} format + // match[2] will contain capture from $var format + std::string var_name = match[1].matched ? match[1].str() : match[2].str(); + + // Get value from system environment variables + const char* env_value = std::getenv(var_name.c_str()); + std::string value = env_value ? env_value : ""; + + result = result.replace(match.position(), match.length(), value); + } + + return result; +} + +} // namespace dropshell diff --git a/src/utils/envmanager.hpp b/src/utils/envmanager.hpp new file mode 100644 index 0000000..149c621 --- /dev/null +++ b/src/utils/envmanager.hpp @@ -0,0 +1,49 @@ +#ifndef ENV_MANAGER_HPP +#define ENV_MANAGER_HPP + +#include +#include +namespace dropshell { + +// envmanager is a class that manages the environment files for the application. +// it is responsible for loading the environment files, and providing a class to access the variables. +// it can also save the environment files. +class envmanager { + public: + envmanager(std::string path); + ~envmanager(); + + // load all variables from the environment file + bool load(); + + // save all variables to the environment file + void save(); + + // get variables from the environment files. Trim whitespace from the values. + // keys are case-sensitive. + std::string get_variable(std::string key) const; + void get_all_variables(std::map& variables) const; + + // get variables, but replace patterns ${var} and $var with the actual environment variable in the returned string. + // trim whitespace from the values. + std::string get_variable_substituted(std::string key) const; + void get_all_variables_substituted(std::map& variables) const; + + // add variables to the environment files. + // trim whitespace from the values. + void add_variables(std::map variables); + void set_variable(std::string key, std::string value); + void clear_variables(); + + private: + std::string trim(std::string str) const; + std::string expand_patterns(std::string str) const; + + private: + std::string m_path; + std::map m_variables; +}; + +} // namespace dropshell + +#endif diff --git a/templates/squashkiwi/example/example.env b/templates/squashkiwi/example/example.env deleted file mode 100644 index a16f0d5..0000000 --- a/templates/squashkiwi/example/example.env +++ /dev/null @@ -1,16 +0,0 @@ -# This file contains environment variables specific to the server where the application will be deployed. -# Copy this file to .env and modify the values according to your server's configuration. -# Do not commit the actual .env file to version control as it may contain sensitive information. - -# Application settings -CONTAINER_PORT=8181 -HOST_PORT=80 - -# Deployment settings -LOCAL_DATA_FOLDER="${HOME}/.sk" -CONTAINER_NAME="squashkiwi" - -# Image settings -IMAGE_REGISTRY="gitea.jde.nz" -IMAGE_REPO="squashkiwi/squashkiwi" -IMAGE_TAG="latest" diff --git a/templates/squashkiwi/example/service.env b/templates/squashkiwi/example/service.env new file mode 100644 index 0000000..2437e58 --- /dev/null +++ b/templates/squashkiwi/example/service.env @@ -0,0 +1,15 @@ +# Service settings +TEMPLATE=squashkiwi + +# Application settings +CONTAINER_PORT=8181 +HOST_PORT=80 + +# Deployment settings +LOCAL_DATA_FOLDER="${HOME}/.sk" +CONTAINER_NAME="squashkiwi" + +# Image settings +IMAGE_REGISTRY="gitea.jde.nz" +IMAGE_REPO="squashkiwi/squashkiwi" +IMAGE_TAG="latest"