From ae5a6fabe906fde242a316ddd88830216e63bbbe Mon Sep 17 00:00:00 2001 From: j Date: Fri, 2 Jan 2026 22:27:40 +1300 Subject: [PATCH] feat: Update 7 files --- source/agent-remote/all_status.sh | 21 +++++++++++++++++++-- source/agent-remote/ds_run.sh | 18 ++++++++++++++++++ source/cmake_prebuild.sh | 7 +++++++ source/src/commands/shared_commands.cpp | 18 ++++++++++++++++++ source/src/servers.cpp | 5 +++++ source/src/utils/directories.cpp | 24 ++++++++++++++++++++++++ source/src/utils/directories.hpp | 4 ++++ 7 files changed, 95 insertions(+), 2 deletions(-) diff --git a/source/agent-remote/all_status.sh b/source/agent-remote/all_status.sh index ad638c5..1188790 100755 --- a/source/agent-remote/all_status.sh +++ b/source/agent-remote/all_status.sh @@ -3,10 +3,12 @@ set -uo pipefail # all_status.sh - Get status and ports for all services on this server # -# Usage: all_status.sh +# Usage: all_status.sh [EXPECTED_AGENT_HASH] # # Output: JSON object with status and ports for each service # { +# "agent_hash": "hash-of-remote-agent", +# "agent_match": true|false, # "services": [ # { # "name": "service-name", @@ -24,6 +26,17 @@ DROPSHELL_DIR="$(dirname "${AGENT_PATH}")" SERVICES_DIR="${DROPSHELL_DIR}/services" +# -- Get agent hash -- +EXPECTED_HASH="${1:-}" +LOCAL_HASH="" +AGENT_MATCH="true" +if [[ -f "${AGENT_PATH}/agent.hash" ]]; then + LOCAL_HASH=$(cat "${AGENT_PATH}/agent.hash" | tr -d '[:space:]') +fi +if [[ -n "${EXPECTED_HASH}" && "${LOCAL_HASH}" != "${EXPECTED_HASH}" ]]; then + AGENT_MATCH="false" +fi + # -- Helper to escape JSON strings -- json_escape() { local str="$1" @@ -37,7 +50,11 @@ json_escape() { } # -- Start JSON output -- -echo -n '{"services":[' +echo -n '{"agent_hash":"' +json_escape "$LOCAL_HASH" +echo -n '","agent_match":' +echo -n "$AGENT_MATCH" +echo -n ',"services":[' first=true diff --git a/source/agent-remote/ds_run.sh b/source/agent-remote/ds_run.sh index 4c33178..04dfbb9 100755 --- a/source/agent-remote/ds_run.sh +++ b/source/agent-remote/ds_run.sh @@ -38,6 +38,24 @@ if [[ ! -f "${AGENT_PATH}/common.sh" ]]; then fi source "${AGENT_PATH}/common.sh" +# -- Check agent hash (if AGENT_HASH is provided by dropshell) -- +# Exit code 199 = agent mismatch (special code for dropshell to detect) +if [[ -n "${AGENT_HASH:-}" ]]; then + LOCAL_HASH_FILE="${AGENT_PATH}/agent.hash" + if [[ -f "${LOCAL_HASH_FILE}" ]]; then + LOCAL_HASH=$(cat "${LOCAL_HASH_FILE}" | tr -d '[:space:]') + EXPECTED_HASH=$(echo "${AGENT_HASH}" | tr -d '[:space:]') + if [[ "${LOCAL_HASH}" != "${EXPECTED_HASH}" ]]; then + echo "AGENT_MISMATCH:${EXPECTED_HASH}:${LOCAL_HASH}" >&2 + exit 199 + fi + else + # No local hash file - agent is outdated (pre-hash version) + echo "AGENT_MISMATCH:${AGENT_HASH}:no_hash_file" >&2 + exit 199 + fi +fi + # -- Validate arguments -- if [[ $# -lt 2 ]]; then echo "Usage: ds_run.sh SERVICE COMMAND [args...]" >&2 diff --git a/source/cmake_prebuild.sh b/source/cmake_prebuild.sh index efd5ab5..8d23693 100755 --- a/source/cmake_prebuild.sh +++ b/source/cmake_prebuild.sh @@ -21,6 +21,13 @@ chmod +x "${TEMP_DIR}/dehydrate" # Ensure autogen directory exists mkdir -p "${SCRIPT_DIR}/src/autogen" +# Generate agent hash BEFORE dehydration +# Hash all files in agent-remote (excluding agent.hash itself) to create a content fingerprint +echo "Generating agent hash..." +AGENT_HASH=$(find "${SCRIPT_DIR}/agent-remote" -type f ! -name 'agent.hash' -exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1) +echo "${AGENT_HASH}" > "${SCRIPT_DIR}/agent-remote/agent.hash" +echo "Agent hash: ${AGENT_HASH}" + # Run dehydrate from temp location echo "Running dehydrate from temporary location" "${TEMP_DIR}/dehydrate" "${SCRIPT_DIR}/agent-remote" "${SCRIPT_DIR}/src/autogen" diff --git a/source/src/commands/shared_commands.cpp b/source/src/commands/shared_commands.cpp index b8029f8..cc53d43 100644 --- a/source/src/commands/shared_commands.cpp +++ b/source/src/commands/shared_commands.cpp @@ -171,6 +171,13 @@ namespace dropshell // Run all_status.sh on the remote server to get all service statuses in one call std::string agent_path = remotepath(server_name, user).agent(); std::string script_path = agent_path + "/all_status.sh"; + + // Pass expected agent hash as argument for version checking + std::string expected_hash = get_local_agent_hash(); + if (!expected_hash.empty()) { + script_path += " " + expected_hash; + } + std::string output; sCommand cmd(agent_path, script_path, {}); @@ -185,6 +192,17 @@ namespace dropshell try { nlohmann::json json_response = nlohmann::json::parse(output); + // Check agent hash match + if (json_response.contains("agent_match") && json_response["agent_match"].is_boolean()) { + if (!json_response["agent_match"].get()) { + std::string remote_hash = json_response.contains("agent_hash") ? + json_response["agent_hash"].get() : "unknown"; + warning << "Agent mismatch on " << server_name << " (remote: " << remote_hash.substr(0, 8) + << "..., expected: " << expected_hash.substr(0, 8) << "...)" << std::endl; + warning << "Run 'ds install " << server_name << "' to update the agent" << std::endl; + } + } + if (!json_response.contains("services") || !json_response["services"].is_array()) { debug << "Invalid JSON response from all_status.sh" << std::endl; return status; diff --git a/source/src/servers.cpp b/source/src/servers.cpp index 62ec85d..6ceb5d6 100644 --- a/source/src/servers.cpp +++ b/source/src/servers.cpp @@ -312,6 +312,11 @@ namespace dropshell if (!scommand.has_value()) return false; + // Add agent hash for version checking on remote + std::string agent_hash = get_local_agent_hash(); + if (!agent_hash.empty()) + scommand->add_env_var("AGENT_HASH", agent_hash); + // add the extra env vars to the command for (const auto &[key, value] : extra_env_vars) scommand->add_env_var(key, value); diff --git a/source/src/utils/directories.cpp b/source/src/utils/directories.cpp index cbb6ec0..5c1538f 100644 --- a/source/src/utils/directories.cpp +++ b/source/src/utils/directories.cpp @@ -4,6 +4,7 @@ #include "output.hpp" #include +#include #include #include @@ -37,8 +38,31 @@ namespace dropshell return localpath::agent_local() + "/bb64"; } + std::string agent_hash() + { + return localpath::agent_remote() + "/agent.hash"; + } + } // namespace localfile + std::string get_local_agent_hash() + { + std::string hash_file = localfile::agent_hash(); + if (!std::filesystem::exists(hash_file)) + return ""; + + std::ifstream f(hash_file); + if (!f.is_open()) + return ""; + + std::string hash; + std::getline(f, hash); + // Trim whitespace + hash.erase(0, hash.find_first_not_of(" \t\n\r")); + hash.erase(hash.find_last_not_of(" \t\n\r") + 1); + return hash; + } + // ------------------------------------------------------------------------------------------ namespace localpath diff --git a/source/src/utils/directories.hpp b/source/src/utils/directories.hpp index fd42e66..0d5ef7d 100644 --- a/source/src/utils/directories.hpp +++ b/source/src/utils/directories.hpp @@ -56,8 +56,12 @@ namespace dropshell { std::string server_json(const std::string &server_name); std::string service_env(const std::string &server_name, const std::string &service_name); std::string bb64(); + std::string agent_hash(); // Returns path to agent.hash file } // namespace localfile + // Get the content of the local agent hash (empty string if not found) + std::string get_local_agent_hash(); + namespace localpath { std::string dropshell_dir();