diff --git a/source/agent-remote/_allservicesstatus.sh b/source/agent-remote/_allservicesstatus.sh deleted file mode 100755 index dad2b3d..0000000 --- a/source/agent-remote/_allservicesstatus.sh +++ /dev/null @@ -1,170 +0,0 @@ -#!/bin/bash - -# This script checks ALL services on the server and returns a status for each. - -# Return format is simple ENV with the following format: -# SERVICE_NAME_HEALTH=healthy|unhealthy|unknown -# SERVICE_NAME_PORTS=port1,port2,port3 - -# Get all services on the server -SCRIPT_DIR="$(dirname "$0")" - -# export the passed variables from C++, so subshell can use them. -export AGENT_PATH="${AGENT_PATH}" -export HOST_NAME="${HOST_NAME}" -export SERVER="${SERVER}" - - # //------------------------------------------------------------------------------------------------ - # // remote paths - # // DROPSHELL_DIR - # // |-- backups - # // |-- temp_files - # // |-- agent - # // | |-- bb64 - # // | |-- (other agent files, including _allservicesstatus.sh) - # // |-- services - # // |-- service name - # // |-- config - # // |-- service.env (actual service config) - # // |-- .template_info.env - # // |-- template - # // |-- (script files) - # // |-- config - # // |-- service.env (default service config) - # // |-- .template_info.env - # // |-- (other config files for specific server&service) - -# Get all services on the server -SERVICES_PATH=$(realpath "${SCRIPT_DIR}/../services/") - -CURRENT_OUTPUT="" -CURRENT_EXIT_CODE=0 - -load_dotenv(){ - local file_path=$1 - if [ -f "${file_path}" ]; then - # shellcheck source=/dev/null - source "${file_path}" - fi -} - -_check_required_env_vars_allservicesstatus() { - local required_vars=("$@") - for var in "${required_vars[@]}"; do - if [ -z "${!var}" ]; then - _die "Required environment variable $var is not set" - fi - done -} - -function run_command() { - local service_path=$1 - local command=$2 - local capture_output=${3:-false} # default to false if not specified - - # check if the command is a file - if [ ! -f "${service_path}/template/${command}.sh" ]; then - return; - fi - - # run the command in a subshell to prevent environment changes - # This subshell creates a completely isolated environment for each service - CURRENT_OUTPUT=$( - # Start with a clean environment for each service - set -a - - # First set the core variables that other env vars might reference - export SERVER="${SERVER}" - export SERVICE_NAME="" # Will be set from .template_info.env - export SERVICE="" # Will be set after loading SERVICE_NAME - export DOCKER_CLI_HINTS=false - export AGENT_PATH="${AGENT_PATH}" - export HOST_NAME="${HOST_NAME}" - - # Load the template info first to get SERVICE_NAME and TEMPLATE - load_dotenv "${service_path}/config/.template_info.env" - - # Now set SERVICE from SERVICE_NAME (critical for CONTAINER_NAME expansion) - export SERVICE="${SERVICE_NAME}" - - # Set CONFIG_PATH before loading service.env as it might be referenced - export CONFIG_PATH="${service_path}/config" - - # Now load service.env which can properly expand variables like CONTAINER_NAME=${SERVICE} - load_dotenv "${service_path}/config/service.env" - - set +a - - _check_required_env_vars_allservicesstatus "CONFIG_PATH" "SERVER" "SERVICE" "AGENT_PATH" "HOST_NAME" "TEMPLATE" - - if [ "$capture_output" = "true" ]; then - # Capture and return output - bash "${service_path}/template/${command}.sh" 2>&1 - else - # Run silently and return exit code - bash "${service_path}/template/${command}.sh" > /dev/null 2>&1 - fi - ) - CURRENT_EXIT_CODE=$? -} - -function command_exists() { - local service_path=$1 - local command=$2 - if [ ! -f "${service_path}/template/${command}.sh" ]; then - return 1 - fi - return 0 -} - - -if [ ! -d "${SERVICES_PATH}" ]; then - echo "Services path does not exist: ${SERVICES_PATH}" - exit 0 -fi - -# Get all service names -SERVICE_NAMES=$(ls "${SERVICES_PATH}") - -# Iterate over all service names -for SERVICE_NAME in ${SERVICE_NAMES}; do - - SERVICE_PATH=$(realpath "${SERVICES_PATH}/${SERVICE_NAME}") - - #-------------------------------- - # Get the service health - if ! command_exists "${SERVICE_PATH}" "status"; then - SERVICE_HEALTH="unknown" - else - # Debug: capture output to see what's failing - run_command "${SERVICE_PATH}" "status" "true" - ERROR_OUTPUT="${CURRENT_OUTPUT}" - run_command "${SERVICE_PATH}" "status" "false" - if [ "${CURRENT_EXIT_CODE}" -eq 0 ]; then - SERVICE_HEALTH="healthy" - else - SERVICE_HEALTH="unhealthy" - # Debug: show error for simple-object-server services - if [ -f "${SERVICE_PATH}/config/.template_info.env" ]; then - source "${SERVICE_PATH}/config/.template_info.env" - if [ "${TEMPLATE}" = "simple-object-server" ]; then - >&2 echo "DEBUG ${SERVICE_NAME}: ${ERROR_OUTPUT}" - fi - fi - fi - fi - - #-------------------------------- - # Get the service ports - if ! command_exists "${SERVICE_PATH}" "ports"; then - SERVICE_PORTS="" - else - run_command "${SERVICE_PATH}" "ports" "true" - SERVICE_PORTS="${CURRENT_OUTPUT}" - fi - - #-------------------------------- - # return the health and ports - echo "${SERVICE_NAME}_HEALTH=${SERVICE_HEALTH}" - echo "${SERVICE_NAME}_PORTS=${SERVICE_PORTS}" -done diff --git a/source/agent-remote/agent-install.sh b/source/agent-remote/agent-install.sh index bf75c67..723fa79 100755 --- a/source/agent-remote/agent-install.sh +++ b/source/agent-remote/agent-install.sh @@ -73,7 +73,6 @@ install_bb64 required_files=( "$AGENT_PATH/bb64" - "$AGENT_PATH/_allservicesstatus.sh" "$AGENT_PATH/common.sh" "$AGENT_PATH/datacommands.sh" ) diff --git a/source/src/commands/shared_commands.cpp b/source/src/commands/shared_commands.cpp index b398b6e..2065c85 100644 --- a/source/src/commands/shared_commands.cpp +++ b/source/src/commands/shared_commands.cpp @@ -1,11 +1,15 @@ #include "shared_commands.hpp" #include +#include +#include +#include #include "utils/utils.hpp" #include "servers.hpp" #include "directories.hpp" #include "services.hpp" #include "servers.hpp" #include "utils/output.hpp" +#include "transwarp.hpp" namespace dropshell { @@ -140,51 +144,42 @@ namespace dropshell std::map status; std::string server_name = server_env.get_server_name(); - std::string output; - std::string agentpath = remotepath(server_name,user).agent(); - if (!execute_ssh_command(server_env.get_SSH_INFO(user), - sCommand(agentpath, "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", agentpath}}), - cMode::Silent, - &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) - { - 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); - - 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)); - } - } + // Get all services for this user on this server + std::vector services = get_server_services_info(server_name); + + // Filter services for this specific user + std::vector user_services; + for (const auto& service : services) { + if (service.user == user) { + user_services.push_back(service); } } + + // Use parallel execution to check all services simultaneously + // This matches how the list command already works for multiple servers + if (user_services.size() > 0) { + std::mutex status_mutex; + + // transwarp requires at least 1 thread + size_t num_threads = std::max(size_t(1), user_services.size()); + transwarp::parallel exec{num_threads}; + auto task = transwarp::for_each(exec, user_services.begin(), user_services.end(), + [&status, &status_mutex, &server_name](const LocalServiceInfo& service) { + ServiceStatus service_status; + + // Get health status using the existing is_healthy function + service_status.health = is_healthy(server_name, service.service_name); + + // Get ports using the new get_ports function + service_status.ports = get_ports(server_name, service.service_name); + + // Thread-safe update of the status map + std::lock_guard lock(status_mutex); + status[service.service_name] = service_status; + }); + + task->wait(); + } return status; } @@ -258,6 +253,53 @@ namespace dropshell return HealthStatus::HEALTHY; } + // ------------------------------------------------------------------------------------------------ + // get_ports : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::vector get_ports(const std::string &server, const std::string &service) + { + std::vector ports; + + ServerConfig env(server); + if (!env.is_valid()) + { + error << "Server service not initialized" << std::endl; + return ports; + } + + std::string user = env.get_user_for_service(service); + + // Check if ports script exists + std::string script_path = remotepath(server,user).service_template(service) + "/ports.sh"; + if (!env.check_remote_file_exists(script_path, user)) + { + return ports; // No ports script, return empty + } + + // Run ports script and capture output + std::string output; + if (env.run_remote_template_command_and_capture_output(service, "ports", {}, output, true, {})) + { + // Parse the output - expecting comma-separated or newline-separated port numbers + std::stringstream ss(output); + std::string port_str; + while (ss >> port_str) + { + // Remove any commas + port_str.erase(std::remove(port_str.begin(), port_str.end(), ','), port_str.end()); + try { + int port = std::stoi(port_str); + if (port > 0 && port <= 65535) + ports.push_back(port); + } catch (...) { + // Ignore non-numeric entries + } + } + } + + return ports; + } + // ------------------------------------------------------------------------------------------------ // healthmark : SHARED COMMAND // ------------------------------------------------------------------------------------------------ diff --git a/source/src/commands/shared_commands.hpp b/source/src/commands/shared_commands.hpp index 013f964..b5cead3 100644 --- a/source/src/commands/shared_commands.hpp +++ b/source/src/commands/shared_commands.hpp @@ -67,6 +67,7 @@ namespace dropshell std::string healthtick(const std::string &server, const std::string &service); std::string HealthStatus2String(HealthStatus status); HealthStatus is_healthy(const std::string &server, const std::string &service); + std::vector get_ports(const std::string &server, const std::string &service); std::string healthmark(const std::string &server, const std::string &service); void std_autocomplete(const CommandContext &ctx); diff --git a/source/src/servers.cpp b/source/src/servers.cpp index 39aaf38..cb02566 100644 --- a/source/src/servers.cpp +++ b/source/src/servers.cpp @@ -323,7 +323,8 @@ namespace dropshell for (const auto &[key, value] : extra_env_vars) scommand->add_env_var(key, value); - return execute_ssh_command(get_SSH_INFO(user), scommand.value(), cMode::Defaults, &output); + // Use Silent mode when capturing output to avoid printing to console + return execute_ssh_command(get_SSH_INFO(user), scommand.value(), cMode::Silent, &output); } std::string ServerConfig::get_variable(const std::string &name) const diff --git a/source/src/services.cpp b/source/src/services.cpp index dbfb520..27edab9 100644 --- a/source/src/services.cpp +++ b/source/src/services.cpp @@ -140,7 +140,7 @@ namespace dropshell { // determine if the service template hash matches the template hash. auto it = variables.find("TEMPLATE_HASH"); if (it == variables.end()) - error << "Variable TEMPLATE_HASH not found in the service " << filenames::template_info_env << std::endl; + error << "Variable TEMPLATE_HASH not found in the service " << filenames::template_info_env << " for "<second);