diff --git a/CMakeLists.txt b/CMakeLists.txt index 93cad71..8d2a9c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,15 +7,15 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Find required packages find_package(Boost REQUIRED COMPONENTS program_options filesystem system) +# Auto-detect source files +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE HEADERS "src/*.hpp") + # Add executable -add_executable(dropshell - src/main.cpp - src/version.cpp - src/status.cpp - src/servers.cpp - src/help.cpp - src/config.cpp -) +add_executable(dropshell ${SOURCES}) + +# Set include directories +target_include_directories(dropshell PRIVATE src) # Link libraries target_link_libraries(dropshell PRIVATE diff --git a/src/dropshell-completion.bash b/src/dropshell-completion.bash index 58dd035..73f0049 100755 --- a/src/dropshell-completion.bash +++ b/src/dropshell-completion.bash @@ -7,7 +7,7 @@ _dropshell_completions() { prev="${COMP_WORDS[COMP_CWORD-1]}" # List of main commands - opts="help version status servers" + opts="help version status servers templates" # If we're completing the first argument, show all commands if [[ ${COMP_CWORD} -eq 1 ]] ; then @@ -36,6 +36,10 @@ _dropshell_completions() { COMPREPLY=( $(compgen -W "${servers[@]}" -- ${cur}) ) return 0 ;; + templates) + # No additional completions for templates + COMPREPLY=() + ;; *) ;; esac diff --git a/src/dropshell.cpp b/src/dropshell.cpp new file mode 100644 index 0000000..b664488 --- /dev/null +++ b/src/dropshell.cpp @@ -0,0 +1,27 @@ +#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/dropshell.hpp b/src/dropshell.hpp index eeba8c1..b65fe14 100644 --- a/src/dropshell.hpp +++ b/src/dropshell.hpp @@ -24,6 +24,8 @@ void print_help(const boost::program_options::options_description& desc); void print_version(); void check_status(); void list_servers(); +void list_templates(); +void show_server_details(const std::string& server_name); // Utility functions std::vector get_configured_servers(); diff --git a/src/help.cpp b/src/help.cpp index ff8957d..30e6c64 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -15,10 +15,14 @@ void print_help(const boost::program_options::options_description& desc) { std::cout << std::endl; std::cout << " status Check system status" << std::endl; std::cout << " servers List configured servers" << std::endl; + std::cout << " servers NAME Show details for specific server" << std::endl; + std::cout << " templates List available templates" << 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; } } // namespace dropshell \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6a2422c..977c5df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,7 +51,16 @@ int main(int argc, char* argv[]) { dropshell::check_status(); return 0; } else if (cmd == "servers") { - dropshell::list_servers(); + if (argc > 2) { + // Show details for specific server + dropshell::show_server_details(argv[2]); + } else { + // List all servers + dropshell::list_servers(); + } + return 0; + } else if (cmd == "templates") { + dropshell::list_templates(); return 0; } else if (cmd == "init") { if (!vm.count("directory")) { diff --git a/src/server_env.cpp b/src/server_env.cpp new file mode 100644 index 0000000..035255b --- /dev/null +++ b/src/server_env.cpp @@ -0,0 +1,74 @@ +#include "server_env.hpp" +#include +#include +#include +#include + +namespace dropshell { + +server_env::server_env(const std::string& path) { + // Construct the full path to _server.env + boost::filesystem::path env_path = boost::filesystem::path(path) / "_server.env"; + + // Check if file exists + if (!boost::filesystem::exists(env_path)) { + throw std::runtime_error("Server environment file not found: " + env_path.string()); + } + + try { + // Read the INI file + boost::property_tree::ptree pt; + boost::property_tree::ini_parser::read_ini(env_path.string(), pt); + + // Store all variables + for (const auto& section : pt) { + for (const auto& key_value : section.second) { + variables[key_value.first] = key_value.second.get_value(); + } + } + + // Verify required variables exist + if (variables.find("SSH_HOST") == variables.end() || + variables.find("SSH_USER") == variables.end() || + variables.find("SSH_PORT") == variables.end()) { + throw std::runtime_error("Missing required variables in server environment file"); + } + } catch (const boost::property_tree::ini_parser_error& 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()) { + 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; +} + +std::string server_env::get_SSH_HOST() { + return get_variable("SSH_HOST"); +} + +std::string server_env::get_SSH_USER() { + return get_variable("SSH_USER"); +} + +std::string server_env::get_SSH_PORT() { + return get_variable("SSH_PORT"); +} + +} // namespace dropshell \ No newline at end of file diff --git a/src/server_env.hpp b/src/server_env.hpp new file mode 100644 index 0000000..69b6232 --- /dev/null +++ b/src/server_env.hpp @@ -0,0 +1,32 @@ +// server_env.hpp +// +// read the _server.env file and provide a class to access the variables +// + +#include +#include + +namespace dropshell { + +// reads path / _server.env and provides a class to access the variables. +// each env file is required to have the following variables: +// SSH_HOST +// SSH_USER +// SSH_PORT +// the following replacements are made in the values: +// ${USER} -> the username of the user running dropshell +class server_env { + public: + server_env(const std::string& path); + std::string get_variable(const std::string& name); + + std::string get_SSH_HOST(); + std::string get_SSH_USER(); + std::string get_SSH_PORT(); + + private: + std::map variables; +}; + +} // namespace dropshell + diff --git a/src/servers.cpp b/src/servers.cpp index daefe9c..f5f48ff 100644 --- a/src/servers.cpp +++ b/src/servers.cpp @@ -86,4 +86,67 @@ 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; + 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; + } + + fs::path env_file = server_dir / "_server.env"; + if (!fs::exists(env_file)) { + std::cerr << "Error: Server configuration file not found" << std::endl; + return; + } + + std::cout << "Server Details: " << server_name << std::endl; + std::cout << std::string(40, '-') << std::endl; + + std::ifstream file(env_file.string()); + std::string line; + while (std::getline(file, line)) { + if (!line.empty() && line[0] != '#') { + std::cout << line << std::endl; + } + } + + // Check if server is reachable via SSH + std::string ssh_address; + file.clear(); + file.seekg(0); + while (std::getline(file, line)) { + if (boost::starts_with(line, "SSH_ADDRESS=")) { + ssh_address = line.substr(12); + break; + } + } + + if (!ssh_address.empty()) { + std::cout << std::endl << "Server Status:" << std::endl; + std::cout << std::string(40, '-') << std::endl; + + // Try to connect to the server + std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_address + " 'echo connected' 2>/dev/null"; + int result = system(cmd.c_str()); + if (result == 0) { + std::cout << "Status: Online" << std::endl; + + // Get uptime if possible + cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null"; + int rval = system(cmd.c_str()); + if (rval != 0) { + std::cout << "Error: Failed to get uptime" << std::endl; + } + } else { + std::cout << "Status: Offline" << std::endl; + } + } +} + } // namespace dropshell \ No newline at end of file diff --git a/src/templates.cpp b/src/templates.cpp index 286c73e..2d55cdd 100644 --- a/src/templates.cpp +++ b/src/templates.cpp @@ -5,6 +5,8 @@ #include #include +namespace dropshell { + template_manager::template_manager() { // Constructor implementation } @@ -74,3 +76,4 @@ bool template_manager::get_template_info(const std::string& name, template_info& return false; } +} // namespace dropshell \ No newline at end of file diff --git a/src/templates.hpp b/src/templates.hpp index 1bfca10..c75bfca 100644 --- a/src/templates.hpp +++ b/src/templates.hpp @@ -1,4 +1,9 @@ +#include +#include + +namespace dropshell { + class template_info { public: std::string name; @@ -20,3 +25,5 @@ class template_manager { bool get_templates(std::vector& templates); bool get_template_info(const std::string& name, template_info& info); }; + +} // namespace dropshell