diff --git a/source/src/commands/backupdata.cpp b/source/src/commands/backupdata.cpp index e35aeda..439617c 100644 --- a/source/src/commands/backupdata.cpp +++ b/source/src/commands/backupdata.cpp @@ -78,13 +78,15 @@ namespace dropshell return true; // nothing to back up. } + std::string user = server_env.get_user_for_service(server, service); + // Check if basic installed stuff is in place. - std::string remote_service_template_path = remotepath::service_template(server, service); + std::string remote_service_template_path = remotepath(server, user).service_template(service); std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh"; - std::string remote_service_config_path = remotepath::service_config(server, service); - if (!server_env.check_remote_items_exist({remotepath::service(server, service), + std::string remote_service_config_path = remotepath(server, user).service_config(service); + if (!server_env.check_remote_items_exist({remotepath(server, user).service(service), remote_command_script_file, - remotefile::service_env(server, service)})) + remotefile(server, user).service_env(service)}, user)) { error << "Error: Required service directories not found on remote server" << std::endl; info << "Is the service installed?" << std::endl; @@ -92,10 +94,10 @@ namespace dropshell } // Create backups directory on server if it doesn't exist - std::string remote_backups_dir = remotepath::backups(server); + std::string remote_backups_dir = remotepath(server, user).backups(); debug << "Remote backups directory on " << server << ": " << remote_backups_dir << std::endl; std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir); - if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Defaults)) + if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", mkdir_cmd, {}), cMode::Defaults)) { error << "Failed to create backups directory on server" << std::endl; return false; @@ -129,7 +131,7 @@ namespace dropshell ASSERT(3 == count_substring(magic_string(), local_backup_file_path), "Invalid backup filename"); { // Run backup script - shared_commands::cRemoteTempFolder remote_temp_folder(server_env); + shared_commands::cRemoteTempFolder remote_temp_folder(server_env, user); if (!server_env.run_remote_template_command(service, command, {}, false, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) { error << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; diff --git a/source/src/commands/destroy.cpp b/source/src/commands/destroy.cpp index ccd4e5a..dbb9fa4 100644 --- a/source/src/commands/destroy.cpp +++ b/source/src/commands/destroy.cpp @@ -63,7 +63,8 @@ namespace dropshell if (!SIvalid(service_info)) error << "Invalid service: " << service << std::endl; - if (server_env.check_remote_dir_exists(remotepath::service(server, service))) + std::string user = server_env.get_user_for_service(server, service); + if (server_env.check_remote_dir_exists(remotepath(server, user).service(service), user)) { // run the nuke script on the remote server if it exists. // otherwise just uninstall. @@ -82,16 +83,16 @@ namespace dropshell } // Remove the service directory from the server, running in a docker container as root. - if (server_env.remove_remote_dir(remotepath::service(server, service), true)) + if (server_env.remove_remote_dir(remotepath(server, user).service(service), true, user)) { - ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall"); - info << "Remote service directory removed: " << remotepath::service(server, service) << std::endl; + ASSERT(!server_env.check_remote_dir_exists(remotepath(server, user).service(service), user), "Service directory still found on server after uninstall"); + info << "Remote service directory removed: " << remotepath(server, user).service(service) << std::endl; } else warning << "Failed to remove remote service directory" << std::endl; } else - warning << "Service not found on remote server: " << remotepath::service(server, service) << std::endl; + warning << "Service not found on remote server: " << remotepath(server, user).service(service) << std::endl; } else warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl; diff --git a/source/src/commands/list.cpp b/source/src/commands/list.cpp index 4a994ca..fe484e1 100644 --- a/source/src/commands/list.cpp +++ b/source/src/commands/list.cpp @@ -123,7 +123,7 @@ void show_server_details(const std::string& server_name) { //--------------------- // Check if server is reachable via SSH std::string ssh_address = env.get_SSH_HOST(); - std::string ssh_user = env.get_SSH_USER(); + std::string ssh_user = env.get_SSH_UNPRIVILEGED_USER(); std::string ssh_port = env.get_SSH_PORT(); if (!ssh_address.empty()) { info << std::endl << "Server Status:" << std::endl; diff --git a/source/src/commands/restoredata.cpp b/source/src/commands/restoredata.cpp index 2e880b6..7064487 100644 --- a/source/src/commands/restoredata.cpp +++ b/source/src/commands/restoredata.cpp @@ -195,7 +195,8 @@ namespace dropshell { // restore service from backup info << "5) Restoring service data from backup..." << std::endl; - std::string remote_backups_dir = remotepath::backups(server); + std::string user = server_env.get_user_for_service(server, service); + std::string remote_backups_dir = remotepath(server, user).backups(); std::string remote_backup_file_path = remote_backups_dir + "/" + backup_details->get_filename(); debug << "Copying backup file from local to server: " << local_backup_file_path << " -> " << remote_backup_file_path << std::endl; @@ -207,7 +208,7 @@ namespace dropshell return 1; } - shared_commands::cRemoteTempFolder remote_temp_folder(server_env); + shared_commands::cRemoteTempFolder remote_temp_folder(server_env,user); debug << "Running restore script on server: " << server << std::endl; debug << " BACKUP_FILE: " << remote_backup_file_path << std::endl; diff --git a/source/src/commands/shared_commands.cpp b/source/src/commands/shared_commands.cpp index a17718b..dbaf97f 100644 --- a/source/src/commands/shared_commands.cpp +++ b/source/src/commands/shared_commands.cpp @@ -61,7 +61,7 @@ namespace dropshell std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " + quote(local_path + "/") + " " + - quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + + quote(server_env.get_SSH_UNPRIVILEGED_USER() + "@" + server_env.get_SSH_HOST() + ":" + remote_path + "/"); return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); } @@ -84,11 +84,12 @@ namespace dropshell // ------------------------------------------------------------------------------------------------ // cRemoteTempFolder : SHARED CLASS // ------------------------------------------------------------------------------------------------ - cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env) + cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env, std::string user) : + mServerEnv(server_env), mUser(user) { - std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10); + std::string p = remotepath(server_env.get_server_name(),user).temp_files() + "/" + random_alphanumeric_string(10); std::string mkdir_cmd = "mkdir -p " + quote(p); - if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) + if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", mkdir_cmd, {}), cMode::Silent)) error << "Failed to create temp directory on server" << std::endl; else mPath = p; @@ -97,7 +98,7 @@ namespace dropshell cRemoteTempFolder::~cRemoteTempFolder() { std::string rm_cmd = "rm -rf " + quote(mPath); - execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent); + execute_ssh_command(mServerEnv.get_SSH_INFO(mUser), sCommand("", rm_cmd, {}), cMode::Silent); } std::string cRemoteTempFolder::path() const @@ -119,49 +120,54 @@ namespace dropshell return status; } - std::string output; - if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}), - cMode::Silent, - &output)) - return status; + for (const auto& user : env.get_users()) { + std::string output; + std::string agentpath = remotepath(server_name,user.user).agent(); + if (!execute_ssh_command(env.get_SSH_INFO(user.user), + sCommand(agentpath, "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", agentpath}}), + cMode::Silent, + &output)) + return status; - std::stringstream ss(output); - std::string line; - while (std::getline(ss, line)) - { - std::string key, value; - std::size_t pos = line.find("="); - if (pos != std::string::npos) + std::stringstream ss(output); + std::string line; + while (std::getline(ss, line)) { - key = dequote(trim(line.substr(0, pos))); - value = dequote(trim(line.substr(pos + 1))); + std::string key, value; + std::size_t pos = line.find("="); + if (pos != std::string::npos) + { + key = dequote(trim(line.substr(0, pos))); + value = dequote(trim(line.substr(pos + 1))); - // decode key, it's of format SERVICENAME_[HEALTH|PORTS] - std::string service_name = key.substr(0, key.find_last_of("_")); - std::string status_type = key.substr(key.find_last_of("_") + 1); + // decode key, it's of format SERVICENAME_[HEALTH|PORTS] + std::string service_name = key.substr(0, key.find_last_of("_")); + std::string status_type = key.substr(key.find_last_of("_") + 1); - if (status_type == "HEALTH") - { // healthy|unhealthy|unknown - if (value == "healthy") - status[service_name].health = HealthStatus::HEALTHY; - else if (value == "unhealthy") - status[service_name].health = HealthStatus::UNHEALTHY; - else if (value == "unknown") - status[service_name].health = HealthStatus::UNKNOWN; - else - status[service_name].health = HealthStatus::ERROR; - } - else if (status_type == "PORTS") - { // port1,port2,port3 - std::vector ports = string2multi(value); - for (const auto &port : ports) - { - if (port != "unknown") - status[service_name].ports.push_back(str2int(port)); + if (status_type == "HEALTH") + { // healthy|unhealthy|unknown + if (value == "healthy") + status[service_name].health = HealthStatus::HEALTHY; + else if (value == "unhealthy") + status[service_name].health = HealthStatus::UNHEALTHY; + else if (value == "unknown") + status[service_name].health = HealthStatus::UNKNOWN; + else + status[service_name].health = HealthStatus::ERROR; + } + else if (status_type == "PORTS") + { // port1,port2,port3 + std::vector ports = string2multi(value); + for (const auto &port : ports) + { + if (port != "unknown") + status[service_name].ports.push_back(str2int(port)); + } } } } } + return status; } @@ -215,13 +221,15 @@ namespace dropshell return HealthStatus::ERROR; } - if (!env.check_remote_dir_exists(remotepath::service(server, service))) + std::string user = env.get_user_for_service(server, service); + + if (!env.check_remote_dir_exists(remotepath(server,user).service(service), user)) { return HealthStatus::NOTINSTALLED; } - std::string script_path = remotepath::service_template(server, service) + "/status.sh"; - if (!env.check_remote_file_exists(script_path)) + std::string script_path = remotepath(server,user).service_template(service) + "/status.sh"; + if (!env.check_remote_file_exists(script_path, user)) { return HealthStatus::UNKNOWN; } @@ -307,7 +315,7 @@ namespace dropshell return false; } ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp."); - std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + quote(local_path) + " " + server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : ""); + std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + quote(local_path) + " " + server_env.get_SSH_UNPRIVILEGED_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : ""); return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); } @@ -322,7 +330,7 @@ namespace dropshell return false; } ASSERT(!remote_path.empty() && !local_path.empty(), "Remote or local path not specified. Can't scp."); - std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : ""); + std::string scp_cmd = "scp -P " + server_env.get_SSH_PORT() + " " + server_env.get_SSH_UNPRIVILEGED_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : ""); return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); } diff --git a/source/src/commands/shared_commands.hpp b/source/src/commands/shared_commands.hpp index ccb7598..7bfc800 100644 --- a/source/src/commands/shared_commands.hpp +++ b/source/src/commands/shared_commands.hpp @@ -31,12 +31,13 @@ namespace dropshell class cRemoteTempFolder { public: - cRemoteTempFolder(const server_env_manager &server_env); // create a temp folder on the remote server + cRemoteTempFolder(const server_env_manager &server_env, std::string user); // create a temp folder on the remote server ~cRemoteTempFolder(); // delete the temp folder on the remote server std::string path() const; // get the path to the temp folder on the remote server private: std::string mPath; const server_env_manager &mServerEnv; + std::string mUser; }; bool rsync_tree_to_remote( diff --git a/source/src/commands/ssh.cpp b/source/src/commands/ssh.cpp index 4793365..84930c5 100644 --- a/source/src/commands/ssh.cpp +++ b/source/src/commands/ssh.cpp @@ -7,6 +7,7 @@ #include "services.hpp" #include "servers.hpp" #include "templates.hpp" +#include "assert.hpp" namespace dropshell { @@ -24,8 +25,8 @@ namespace dropshell ssh_handler, shared_commands::std_autocomplete, false, // hidden - true, // requires_config - true, // requires_install + true, // requires_config + true, // requires_install 1, // min_args (after command) 2, // max_args (after command) "ssh SERVER", @@ -34,13 +35,12 @@ namespace dropshell ssh SERVER SERVICE SSH into a docker container for a service. ssh SERVER SSH into a server. + ssh USER@SERVER SSH into a server as a specific user. )"}); } } ssh_command_register; - - - bool ssh_into_server(const std::string &server) + bool ssh_into_server(const std::string &server, std::string user) { server_env_manager server_env(server); if (!server_env.is_valid()) @@ -48,7 +48,7 @@ namespace dropshell error << "Server " << server << " is not valid" << std::endl; return false; } - execute_ssh_command(server_env.get_SSH_INFO(), sCommand(remotepath::DROPSHELL_DIR(server), "ls --color && bash", {}), cMode::Interactive); + execute_ssh_command(server_env.get_SSH_INFO(user), sCommand(remotepath(server, user).DROPSHELL_DIR(), "ls --color && bash", {}), cMode::Interactive); return true; } @@ -80,7 +80,7 @@ namespace dropshell return false; } - server_env.run_remote_template_command(service,"ssh",{},false,{}); // explicitly supports interactive ssh! + server_env.run_remote_template_command(service, "ssh", {}, false, {}); // explicitly supports interactive ssh! return true; } @@ -92,18 +92,41 @@ namespace dropshell return 1; } - std::string server = safearg(ctx.args, 0); - + // ssh into the server if (ctx.args.size() < 2) { - // ssh into the server - return ssh_into_server(server) ? 0 : 1; + std::string arg1 = safearg(ctx.args, 0); + std::string server, user; + + // parse either user@server or server + if (arg1.find("@") != std::string::npos) + { + user = arg1.substr(0, arg1.find("@")); + server = arg1.substr(arg1.find("@") + 1); + } + else + { + server = arg1; + + // get the first user from the server.env file, and ssh in as that user. + server_env_manager server_env(server); + if (!server_env.is_valid()) + { + error << "Server " << server << " is not valid" << std::endl; + return 1; + } + ASSERT(server_env.get_users().size() > 0, "Server " + server + " has no users"); + user = server_env.get_users()[0].user; + } + + return ssh_into_server(server, user) ? 0 : 1; + } + else + { // ssh into a service on the server. + std::string server = safearg(ctx.args, 0); + std::string service = safearg(ctx.args, 1); + return ssh_into_service(server, service) ? 0 : 1; } - - std::string service = safearg(ctx.args, 1); - - // ssh into the specific service. - return ssh_into_service(server, service) ? 0 : 1; } } // namespace dropshell \ No newline at end of file diff --git a/source/src/commands/uninstall.cpp b/source/src/commands/uninstall.cpp index e09ad99..b19ae25 100644 --- a/source/src/commands/uninstall.cpp +++ b/source/src/commands/uninstall.cpp @@ -53,23 +53,25 @@ namespace dropshell return false; // should never hit this. } + std::string user = server_env.get_user_for_service(server, service); + // 2. Check if service directory exists on server - if (!server_env.check_remote_dir_exists(remotepath::service(server, service))) + if (!server_env.check_remote_dir_exists(remotepath(server, user).service(service), user)) { error << "Service is not installed: " << service << std::endl; return true; // Nothing to uninstall } // 3. Run uninstall script if it exists - std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh"; + std::string uninstall_script = remotepath(server, user).service_template(service) + "/uninstall.sh"; if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {})) warning << "Uninstall script failed, but continuing with directory removal" << std::endl; // 4. Remove the service directory from the server, running in a docker container as root. - if (server_env.remove_remote_dir(remotepath::service(server, service), false)) + if (server_env.remove_remote_dir(remotepath(server, user).service(service), false, user)) { - ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall"); - info << "Removed remote service directory " << remotepath::service(server, service) << std::endl; + ASSERT(!server_env.check_remote_dir_exists(remotepath(server, user).service(service), user), "Service directory still found on server after uninstall"); + info << "Removed remote service directory " << remotepath(server, user).service(service) << std::endl; } else warning << "Failed to remove remote service directory" << std::endl; diff --git a/source/src/server_env_manager.cpp b/source/src/server_env_manager.cpp index 9cd5303..9d6e70d 100644 --- a/source/src/server_env_manager.cpp +++ b/source/src/server_env_manager.cpp @@ -6,6 +6,7 @@ #include "utils/utils.hpp" #include "utils/json.hpp" #include "utils/execute.hpp" +#include "utils/assert.hpp" #include #include @@ -25,7 +26,7 @@ server_env_manager::server_env_manager(const std::string& server_name) : mValid( if (server_name.empty()) return; - // Construct the full path to server.env + // Construct the full path to server.json std::string server_env_path = localfile::server_json(server_name); // Check if file exists @@ -53,12 +54,11 @@ server_env_manager::server_env_manager(const std::string& server_name) : mValid( value = var.value() ? "true" : "false"; else value = var.value().dump(); - mVariables[var.key()] = replace_with_environment_variables_like_bash(value); } // Verify required variables exist - for (const auto& var : {"SSH_HOST", "SSH_USER", "SSH_PORT", "DROPSHELL_DIR"}) { + for (const auto& var : {"SSH_HOST", "SSH_PORT", "USERS"}) { if (mVariables.find(var) == mVariables.end()) { // Print the variables identified in the file std::cout << "Variables identified in the file:" << std::endl; @@ -68,6 +68,25 @@ server_env_manager::server_env_manager(const std::string& server_name) : mValid( throw std::runtime_error("Missing required variable: " + std::string(var)); } } + + // Parse users array + if (!server_env_json.contains("USERS") || !server_env_json["USERS"].is_array()) { + std::cerr << "Error: USERS array not found or invalid in server configuration" << std::endl; + return; + } + + for (const auto& user_json : server_env_json["USERS"]) { + UserConfig user; + user.user = user_json["USER"].get(); + user.dir = user_json["DIR"].get(); + mUsers.push_back(user); + } + + if (mUsers.empty()) { + std::cerr << "Error: No users defined in server configuration" << std::endl; + return; + } + mValid = true; } catch (const std::exception& e) { @@ -75,13 +94,21 @@ server_env_manager::server_env_manager(const std::string& server_name) : mValid( } } -bool server_env_manager::create_server_env(const std::string &server_env_path, const std::string &SSH_HOST, const std::string &SSH_USER, const std::string &SSH_PORT, const std::string &DROPSHELL_DIR) +bool server_env_manager::create_server_json_file(const std::string &server_env_path, const std::string &SSH_HOST, const std::string &SSH_PORT, const std::vector& users) { nlohmann::json server_env_json; server_env_json["SSH_HOST"] = SSH_HOST; - server_env_json["SSH_USER"] = SSH_USER; - server_env_json["SSH_PORT"] = SSH_PORT; - server_env_json["DROPSHELL_DIR"] = DROPSHELL_DIR; + server_env_json["SSH_PORT"] = std::stoi(SSH_PORT); + + // Create users array + nlohmann::json users_array = nlohmann::json::array(); + for (const auto& user : users) { + nlohmann::json user_json; + user_json["USER"] = user.user; + user_json["DIR"] = user.dir; + users_array.push_back(user_json); + } + server_env_json["USERS"] = users_array; try { std::ofstream server_env_file(server_env_path); @@ -94,58 +121,52 @@ bool server_env_manager::create_server_env(const std::string &server_env_path, c } } -std::string server_env_manager::get_variable(const std::string& name) const { - auto it = mVariables.find(name); - if (it == mVariables.end()) { - return ""; +std::string server_env_manager::get_user_dir(const std::string& user) const { + for (const auto& u : mUsers) { + if (u.user == user) { + return u.dir; + } } - return it->second; + return ""; } -std::optional server_env_manager::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector args, const bool silent) const + +std::string server_env_manager::get_user_for_service(const std::string &server, const std::string &service) { - if (command.empty()) - return std::nullopt; - - std::string remote_service_template_path = remotepath::service_template(mServerName,service_name); - std::string script_path = remote_service_template_path + "/" + command + ".sh"; - - std::map env_vars; - if (!get_all_service_env_vars(mServerName, service_name, env_vars)) { - std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl; - return std::nullopt; + auto services_info = get_server_services_info(server); + if (std::find_if(services_info.begin(), services_info.end(), + [&service](const LocalServiceInfo &si) { return si.service_name == service; }) != services_info.end()) { + // found a service with matching name. + auto it = std::find_if(services_info.begin(), services_info.end(), + [&service](const LocalServiceInfo &si) { return si.service_name == service; }); + if (it != services_info.end()) { + return it->user; + } } - - std::string argstr = ""; - for (const auto& arg : args) { - argstr += " " + quote(dequote(trim(arg))); - } - - sCommand sc( - remote_service_template_path, - quote(script_path) + argstr + (silent ? " > /dev/null 2>&1" : ""), - env_vars - ); - - if (sc.empty()) { - std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl; - return std::nullopt; - } - return sc; + return ""; } +sSSHInfo server_env_manager::get_SSH_INFO(std::string user) const +{ + ASSERT(!user.empty(), "User is empty, cannot get SSH info."); + // Find user in mUsers vector + auto it = std::find_if(mUsers.begin(), mUsers.end(), + [&user](const UserConfig& u) { return u.user == user; }); + ASSERT(it != mUsers.end(), ("User " + user + " not found in server environment.")); + return sSSHInfo{get_SSH_HOST(), user, get_SSH_PORT(), get_server_name()}; +} -bool server_env_manager::check_remote_dir_exists(const std::string &dir_path) const +bool server_env_manager::check_remote_dir_exists(const std::string &dir_path, std::string user) const { sCommand scommand("", "test -d " + quote(dir_path),{}); - return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent); + return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent); } -bool server_env_manager::check_remote_file_exists(const std::string& file_path) const { +bool server_env_manager::check_remote_file_exists(const std::string& file_path, std::string user) const { sCommand scommand("", "test -f " + quote(file_path),{}); - return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent); + return execute_ssh_command(get_SSH_INFO(user), scommand, cMode::Silent); } -bool server_env_manager::check_remote_items_exist(const std::vector &file_paths) const +bool server_env_manager::check_remote_items_exist(const std::vector &file_paths, std::string user) const { // convert file_paths to a single string, separated by spaces std::string file_paths_str; @@ -157,7 +178,8 @@ bool server_env_manager::check_remote_items_exist(const std::vector // check if all items in the vector exist on the remote server, in a single command. sCommand scommand("", "for item in " + file_paths_str + "; do test -f $item; done",{}); - bool okay = execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent); + dropshell::sSSHInfo sshinfo = get_SSH_INFO(user); + bool okay = execute_ssh_command(sshinfo, scommand, cMode::Silent); if (!okay) { std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl; return false; @@ -165,7 +187,8 @@ bool server_env_manager::check_remote_items_exist(const std::vector return true; } -bool server_env_manager::remove_remote_dir(const std::string &dir_path, bool silent) const +bool server_env_manager::remove_remote_dir( + const std::string &dir_path, bool silent, std::string user) const { std::filesystem::path path(dir_path); std::filesystem::path parent_path = path.parent_path(); @@ -189,11 +212,18 @@ bool server_env_manager::remove_remote_dir(const std::string &dir_path, bool sil sCommand scommand("", remote_cmd,{}); cMode mode = (silent ? cMode::Silent : cMode::Defaults); - return execute_ssh_command(get_SSH_INFO(), scommand, mode); + dropshell::sSSHInfo sshinfo = get_SSH_INFO(user); + return execute_ssh_command(sshinfo, scommand, mode); } -bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector args, bool silent, std::map extra_env_vars) const +bool server_env_manager::run_remote_template_command( + const std::string &service_name, + const std::string &command, + std::vector args, + bool silent, + std::map extra_env_vars) const { + std::string user = get_user_for_service(mServerName, service_name); auto scommand = construct_standard_template_run_cmd(service_name, command, args, silent); if (!scommand.has_value()) return false; @@ -205,11 +235,18 @@ bool server_env_manager::run_remote_template_command(const std::string &service_ if (scommand->get_command_to_run().empty()) return false; cMode mode = (command=="ssh") ? (cMode::Interactive) : (silent ? cMode::Silent : cMode::Defaults); - return execute_ssh_command(get_SSH_INFO(), scommand.value(), mode); + return execute_ssh_command(get_SSH_INFO(user), scommand.value(), mode); } -bool server_env_manager::run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command, std::vector args, std::string &output, bool silent, std::map extra_env_vars) const +bool server_env_manager::run_remote_template_command_and_capture_output( + const std::string &service_name, + const std::string &command, + std::vector args, + std::string &output, + bool silent, + std::map extra_env_vars) const { + std::string user = get_user_for_service(mServerName, service_name); auto scommand = construct_standard_template_run_cmd(service_name, command, args, false); if (!scommand.has_value()) return false; @@ -218,13 +255,67 @@ bool server_env_manager::run_remote_template_command_and_capture_output(const st for (const auto& [key, value] : extra_env_vars) scommand->add_env_var(key, value); - return execute_ssh_command(get_SSH_INFO(), scommand.value(), cMode::Defaults, &output); + return execute_ssh_command(get_SSH_INFO(user), scommand.value(), cMode::Defaults, &output); +} + +std::string server_env_manager::get_variable(const std::string& name) const { + auto it = mVariables.find(name); + if (it == mVariables.end()) { + return ""; + } + return it->second; +} + +std::optional server_env_manager::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector args, const bool silent) const +{ + if (command.empty()) + return std::nullopt; + + std::string user = get_user_for_service(mServerName, service_name); + + std::string remote_service_template_path = remotepath(mServerName,user).service_template(service_name); + std::string script_path = remote_service_template_path + "/" + command + ".sh"; + + std::map env_vars; + if (!get_all_service_env_vars(mServerName, service_name, env_vars)) { + std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl; + return std::nullopt; + } + + std::string argstr = ""; + for (const auto& arg : args) { + argstr += " " + quote(dequote(trim(arg))); + } + + if(env_vars.find("RUNAS") == env_vars.end()) { + error << "Error: RUNAS is not set in .template_info.env for the service." << std::endl; + return std::nullopt; + } + std::string runas = env_vars.find("RUNAS")->second; + if(runas!="root" && runas!="user") { + error << "Error: RUNAS is not set to root or user in .template_info.env for the service." << std::endl; + return std::nullopt; + } + bool run_as_root = runas == "root"; + + if (run_as_root && !get_ALLOW_ROOT_SERVICES()) { + error << "Error: The service " << service_name << " is set to run as root, but the server environment does not allow root services." << std::endl; + return std::nullopt; + } + + sCommand sc( + remote_service_template_path, + quote(script_path) + argstr + (silent ? " > /dev/null 2>&1" : ""), + env_vars, + run_as_root + ); + + if (sc.empty()) { + std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl; + return std::nullopt; + } + return sc; } - -// base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh" -// echo YmFzaCAtYyAnRk9PPUJBUiBXSEVFPVlBWSBiYXNoIC4vdGVzdC5zaCcK | base64 -d | bash - - } // namespace dropshell \ No newline at end of file diff --git a/source/src/server_env_manager.hpp b/source/src/server_env_manager.hpp index bd4a0d9..56e6f3d 100644 --- a/source/src/server_env_manager.hpp +++ b/source/src/server_env_manager.hpp @@ -11,8 +11,17 @@ #include #include "utils/execute.hpp" #include + +#define JSON_INLINE_ALL +#include + namespace dropshell { +struct UserConfig { + std::string user; + std::string dir; +}; + class server_env_manager; // ------------------------------------------------------------------------------------------------ @@ -20,7 +29,7 @@ class server_env_manager; // reads path / server.env and provides a class to access the variables. // each env file is required to have the following variables: // SSH_HOST -// SSH_USER +// SSH_UNPRIVILEGED_USER // SSH_PORT // the following replacements are made in the values: // ${USER} -> the username of the user running dropshell @@ -28,35 +37,40 @@ class server_env_manager { public: server_env_manager(const std::string& server_name); - static bool create_server_env( + static bool create_server_json_file( const std::string& server_env_path, const std::string& SSH_HOST, - const std::string& SSH_USER, const std::string& SSH_PORT, - const std::string& DROPSHELL_DIR); + const std::vector& users); std::string get_variable(const std::string& name) const; // trivial getters. const std::map& get_variables() const { return mVariables; } std::string get_SSH_HOST() const { return get_variable("SSH_HOST"); } - std::string get_SSH_USER() const { return get_variable("SSH_USER"); } std::string get_SSH_PORT() const { return get_variable("SSH_PORT"); } - std::string get_DROPSHELL_DIR() const { return get_variable("DROPSHELL_DIR"); } - sSSHInfo get_SSH_INFO() const { return sSSHInfo{get_SSH_HOST(), get_SSH_USER(), get_SSH_PORT(), get_server_name()}; } + std::string get_SSH_UNPRIVILEGED_USER() const { return get_variable("SSH_UNPRIVILEGED_USER"); } + bool get_ALLOW_ROOT_SERVICES() const { return get_variable("ALLOW_ROOT_SERVICES")=="true"; } + std::vector get_users() const { return mUsers; } + std::string get_user_dir(const std::string& user) const; bool is_valid() const { return mValid; } std::string get_server_name() const { return mServerName; } + static std::string get_user_for_service(const std::string& server, const std::string& service); + + sSSHInfo get_SSH_INFO(std::string user) const; + // helper functions public: - bool check_remote_dir_exists(const std::string &dir_path) const; - bool check_remote_file_exists(const std::string& file_path) const; - bool check_remote_items_exist(const std::vector& file_paths) const; + bool check_remote_dir_exists(const std::string &dir_path, std::string user) const; + bool check_remote_file_exists(const std::string& file_path, std::string user) const; + bool check_remote_items_exist(const std::vector& file_paths, std::string user) const; - bool remove_remote_dir(const std::string &dir_path, bool silent) const; + bool remove_remote_dir(const std::string &dir_path, bool silent, std::string user) const; bool run_remote_template_command(const std::string& service_name, const std::string& command, std::vector args, bool silent, std::map extra_env_vars) const; + bool run_remote_template_command_and_capture_output(const std::string& service_name, const std::string& command, std::vector args, std::string & output, bool silent, std::map extra_env_vars) const; @@ -66,6 +80,7 @@ class server_env_manager { private: std::string mServerName; std::map mVariables; + std::vector mUsers; bool mValid; }; diff --git a/source/src/servers.cpp b/source/src/servers.cpp index 7ac0648..fc5f28f 100644 --- a/source/src/servers.cpp +++ b/source/src/servers.cpp @@ -40,7 +40,7 @@ std::vector get_configured_servers() { servers.push_back({ server_name, env.get_SSH_HOST(), - env.get_SSH_USER(), + env.get_SSH_UNPRIVILEGED_USER(), env.get_SSH_PORT() }); } @@ -65,7 +65,7 @@ ServerInfo get_server_info(const std::string &server_name) std::cerr << "Error: Invalid server environment file: " << server_dir << std::endl; continue; } - return ServerInfo({server_name, env.get_SSH_HOST(), env.get_SSH_USER(), env.get_SSH_PORT()}); + return ServerInfo({server_name, env.get_SSH_HOST(), env.get_SSH_UNPRIVILEGED_USER(), env.get_SSH_PORT()}); } } return ServerInfo(); @@ -98,7 +98,7 @@ bool create_server(const std::string &server_name) std::ofstream server_env_file(server_env_path); server_env_file << "{" << std::endl; server_env_file << " \"SSH_HOST\": \"" << server_name << "\"," << std::endl; - server_env_file << " \"SSH_USER\": \"" << user << "\"," << std::endl; + server_env_file << " \"SSH_UNPRIVILEGED_USER\": \"" << user << "\"," << std::endl; server_env_file << " \"SSH_PORT\": " << 22 << "," << std::endl; server_env_file << " \"DROPSHELL_DIR\": \"" << "/home/"+user+"/.dropshell\"" << std::endl; server_env_file << "}" << std::endl; diff --git a/source/src/servers.hpp b/source/src/servers.hpp index 2c7b2d3..e61c4b1 100644 --- a/source/src/servers.hpp +++ b/source/src/servers.hpp @@ -11,7 +11,6 @@ namespace dropshell { struct ServerInfo { std::string name; std::string ssh_host; - std::string ssh_user; std::string ssh_port; }; diff --git a/source/src/service_runner.cpp b/source/src/service_runner.cpp index 16050ad..892544b 100644 --- a/source/src/service_runner.cpp +++ b/source/src/service_runner.cpp @@ -265,13 +265,13 @@ // bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent) // { -// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : ""); +// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : ""); // return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); // } // bool service_runner::scp_file_from_remote(const std::string &remote_path, const std::string &local_path, bool silent) // { -// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : ""); +// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_UNPRIVILEGED_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : ""); // return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); // } diff --git a/source/src/services.cpp b/source/src/services.cpp index b683ada..63a4864 100644 --- a/source/src/services.cpp +++ b/source/src/services.cpp @@ -19,7 +19,8 @@ namespace dropshell { return !service_info.service_name.empty() && !service_info.template_name.empty() && !service_info.local_service_path.empty() && - !service_info.local_template_path.empty(); + !service_info.local_template_path.empty() && + !service_info.user.empty(); } std::vector get_server_services_info(const std::string& server_name) { @@ -169,14 +170,17 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string if (server_info.ssh_host.empty()) std::cerr << "Error: Server " << server_name << " not found - ssh_host empty, so HOST_NAME not set" << std::endl; + std::string user = server_env_manager::get_user_for_service(server_name, service_name); + // add in some handy variables. // if we change these, we also need to update agent/_allservicesstatus.sh - all_env_vars["CONFIG_PATH"] = remotepath::service_config(server_name,service_name); + all_env_vars["CONFIG_PATH"] = remotepath(server_name,user).service_config(service_name); all_env_vars["SERVER"] = server_name; all_env_vars["SERVICE"] = service_name; - all_env_vars["AGENT_PATH"] = remotepath::agent(server_name); + all_env_vars["AGENT_PATH"] = remotepath(server_name,user).agent(); all_env_vars["HOST_NAME"] = server_info.ssh_host; all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk. + all_env_vars["SSH_USER"] = user; // Lambda function to load environment variables from a file auto load_env_file = [&all_env_vars](const std::string& file) { diff --git a/source/src/services.hpp b/source/src/services.hpp index 18651b7..3817a51 100644 --- a/source/src/services.hpp +++ b/source/src/services.hpp @@ -13,6 +13,7 @@ namespace dropshell { std::string template_name; std::string local_service_path; std::string local_template_path; + std::string user; }; bool SIvalid(const LocalServiceInfo& service_info); diff --git a/source/src/utils/directories.cpp b/source/src/utils/directories.cpp index c8eb446..cfc3a19 100644 --- a/source/src/utils/directories.cpp +++ b/source/src/utils/directories.cpp @@ -161,70 +161,69 @@ namespace localpath { // |-- (other config files for specific server&service) + remotefile::remotefile(const std::string &server_name, const std::string &user) : + mServer_name(server_name), mUser(user) {} -namespace remotefile { - - std::string service_env(const std::string &server_name, const std::string &service_name) + std::string remotefile::service_env(const std::string &service_name) const { - return remotepath::service_config(server_name, service_name) + "/service.env"; + return remotepath(mServer_name,mUser).service_config(service_name) + "/service.env"; } -} -namespace remotepath { - std::string DROPSHELL_DIR(const std::string &server_name) + remotepath::remotepath(const std::string &server_name, const std::string &user) : mServer_name(server_name), mUser(user) {} + + std::string remotepath::DROPSHELL_DIR() const { - return server_env_manager(server_name).get_DROPSHELL_DIR(); + return server_env_manager(mServer_name).get_user_dir(mUser); } - std::string services(const std::string &server_name) + std::string remotepath::services() const { - std::string dsp = DROPSHELL_DIR(server_name); + std::string dsp = DROPSHELL_DIR(); return (dsp.empty() ? "" : (dsp + "/services")); } - std::string service(const std::string &server_name, const std::string &service_name) + std::string remotepath::service(const std::string &service_name) const { - std::string services_path = services(server_name); + std::string services_path = services(); return (services_path.empty() ? "" : (services_path + "/" + service_name)); } - std::string service_config(const std::string &server_name, const std::string &service_name) + std::string remotepath::service_config(const std::string &service_name) const { - std::string service_path = service(server_name, service_name); + std::string service_path = service(service_name); return (service_path.empty() ? "" : (service_path + "/config")); } - std::string service_template(const std::string &server_name, const std::string &service_name) + std::string remotepath::service_template(const std::string &service_name) const { - std::string service_path = service(server_name, service_name); + std::string service_path = service(service_name); return (service_path.empty() ? "" : (service_path + "/template")); } - std::string backups(const std::string &server_name) + std::string remotepath::backups() const { - std::string dsp = DROPSHELL_DIR(server_name); + std::string dsp = DROPSHELL_DIR(); return (dsp.empty() ? "" : (dsp + "/backups")); } - std::string temp_files(const std::string &server_name) + std::string remotepath::temp_files() const { - std::string dsp = DROPSHELL_DIR(server_name); + std::string dsp = DROPSHELL_DIR(); return (dsp.empty() ? "" : (dsp + "/temp_files")); } - std::string agent(const std::string &server_name) + std::string remotepath::agent() const { - std::string dsp = DROPSHELL_DIR(server_name); + std::string dsp = DROPSHELL_DIR(); return (dsp.empty() ? "" : (dsp + "/agent")); } - std::string service_env(const std::string &server_name, const std::string &service_name) - { - std::string service_path = service_config(server_name, service_name); - return (service_path.empty() ? "" : (service_path + "/service.env")); - } -} // namespace remotepath + // std::string remotepath::service_env(const std::string &service_name) const + // { + // std::string service_path = service_config(service_name); + // return (service_path.empty() ? "" : (service_path + "/service.env")); + // } // ------------------------------------------------------------------------------------------ diff --git a/source/src/utils/directories.hpp b/source/src/utils/directories.hpp index 35c9d2f..d03a282 100644 --- a/source/src/utils/directories.hpp +++ b/source/src/utils/directories.hpp @@ -99,20 +99,30 @@ namespace dropshell { // |-- .template_info.env // |-- (other config files for specific server&service) - namespace remotefile { - std::string service_env(const std::string &server_name, const std::string &service_name); - } // namespace remotefile + class remotefile { + public: + remotefile(const std::string &server_name, const std::string &user); + std::string service_env(const std::string &service_name) const; + private: + std::string mServer_name; + std::string mUser; + }; - namespace remotepath { - std::string DROPSHELL_DIR(const std::string &server_name); - std::string services(const std::string &server_name); - std::string service(const std::string &server_name, const std::string &service_name); - std::string service_config(const std::string &server_name, const std::string &service_name); - std::string service_template(const std::string &server_name, const std::string &service_name); - std::string backups(const std::string &server_name); - std::string temp_files(const std::string &server_name); - std::string agent(const std::string &server_name); - } // namespace remotepath + class remotepath { + public: + remotepath(const std::string &server_name, const std::string &user); + std::string DROPSHELL_DIR() const; + std::string services() const; + std::string service(const std::string &service_name) const; + std::string service_config(const std::string &service_name) const; + std::string service_template(const std::string &service_name) const; + std::string backups() const; + std::string temp_files() const; + std::string agent() const; + private: + std::string mServer_name; + std::string mUser; + }; //------------------------------------------------------------------------------------------------ // utility functions diff --git a/source/src/utils/execute.cpp b/source/src/utils/execute.cpp index 4731771..fd1ac97 100644 --- a/source/src/utils/execute.cpp +++ b/source/src/utils/execute.cpp @@ -171,7 +171,7 @@ namespace dropshell std::string remote_bb64_path; if (!hasFlag(mode, cMode::NoBB64)) - remote_bb64_path = remotepath::agent(ssh_info.server_ID) + "/bb64"; + remote_bb64_path = remotepath(ssh_info.server_ID, ssh_info.user).agent() + "/bb64"; bool rval = execute_local_command( "", // local directory to run in diff --git a/source/src/utils/execute.hpp b/source/src/utils/execute.hpp index d8255a6..619f867 100644 --- a/source/src/utils/execute.hpp +++ b/source/src/utils/execute.hpp @@ -41,12 +41,13 @@ bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_comm // class to hold a command to run on the remote server. class sCommand { public: - sCommand(std::string directory_to_run_in, std::string command_to_run, const std::map & env_vars) : - mDir(directory_to_run_in), mCmd(command_to_run), mVars(env_vars) {} + sCommand(std::string directory_to_run_in, std::string command_to_run, const std::map & env_vars, bool requires_root = false) : + mDir(directory_to_run_in), mCmd(command_to_run), mVars(env_vars), mRequiresRoot(requires_root) {} std::string get_directory_to_run_in() const { return mDir; } std::string get_command_to_run() const { return mCmd; } const std::map& get_env_vars() const { return mVars; } + bool requires_root() const { return mRequiresRoot; } void add_env_var(const std::string& key, const std::string& value) { mVars[key] = value; } @@ -58,6 +59,7 @@ class sCommand { std::string makesafecmd(std::string bb64path, const std::string& command) const; private: + bool mRequiresRoot; std::string mDir; std::string mCmd; std::map mVars; diff --git a/source/src/utils/utils.cpp b/source/src/utils/utils.cpp index 574c09a..c2b82eb 100644 --- a/source/src/utils/utils.cpp +++ b/source/src/utils/utils.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace dropshell { @@ -432,4 +433,16 @@ std::string get_line_wrap(std::string &src, int maxchars) return remove_return(out) + '\n'; } +std::string tolower(const std::string& str) { + if (str.empty()) return str; + + std::string result; + result.reserve(str.size()); // Pre-allocate space for efficiency + + for (unsigned char c : str) { + result.push_back(std::tolower(c)); + } + return result; +} + } // namespace dropshell \ No newline at end of file diff --git a/source/src/utils/utils.hpp b/source/src/utils/utils.hpp index c652862..71cdbec 100644 --- a/source/src/utils/utils.hpp +++ b/source/src/utils/utils.hpp @@ -59,4 +59,6 @@ int get_console_width(); std::string get_line_wrap(std::string & src, int maxchars); +std::string tolower(const std::string& str); + } // namespace dropshell \ No newline at end of file