diff --git a/source/agent-local/agent-install.sh b/source/agent-local/agent-install.sh new file mode 100755 index 0000000..8f8faad --- /dev/null +++ b/source/agent-local/agent-install.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +SCRIPT_DIR=$(dirname "$0") + +echo "Installing dropshell host agent on this computer..." + + +# Prints an error message in red and exits with status code 1. +_die() { + echo -e "Error: $1" + exit 1 +} + + +# Checks if listed environment variables are set; calls _die() if any are missing. +_check_required_env_vars() { + local required_vars=("$@") + for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + _die "Required environment variable $var is not set" + fi + done +} + +# Checks if Docker is installed, running, and user has permission. Returns 1 on failure. +_check_docker_installed() { + if ! command -v docker &> /dev/null; then + echo "Docker is not installed" + return 1 + fi + + # check if docker daemon is running + if ! docker info &> /dev/null; then + echo "Docker daemon is not running" + return 1 + fi + + # check if user has permission to run docker + if ! docker run --rm hello-world &> /dev/null; then + echo "User does not have permission to run docker" + return 1 + fi + + return 0 +} + +function install_bb64() { + # check curl installed + if ! command -v curl &> /dev/null; then + _die "Curl is not installed. Curl is required for agent installation." + fi + + curl -fsSL "https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh" | bash -s -- "$AGENT_PATH" "$(id -u $USER):$(id -g $USER)" + + # test result code from curl + if [ $? -ne 0 ]; then + _die "Failed to install bb64. Curl returned non-zero exit code." + fi + + # test if bb64 is installed + "$AGENT_PATH/bb64" -v + if [ $? -ne 0 ]; then + _die "bb64 did not install correctly." + fi + + echo "bb64 installed successfully." + return 0; +} + + +#------------------------------------------------------------------------- + +set -a +AGENT_PATH="$SCRIPT_DIR" +set +a + +_check_required_env_vars "AGENT_PATH" +echo "Installing host agent into $AGENT_PATH" + +_check_docker_installed || _die "Docker is required." + +install_bb64 + diff --git a/source/agent/_allservicesstatus.sh b/source/agent-remote/_allservicesstatus.sh similarity index 100% rename from source/agent/_allservicesstatus.sh rename to source/agent-remote/_allservicesstatus.sh diff --git a/source/agent-remote/agent-install.sh b/source/agent-remote/agent-install.sh new file mode 100755 index 0000000..56f90eb --- /dev/null +++ b/source/agent-remote/agent-install.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# This script is used to install the dropshell agent on a remote server. + +SCRIPT_DIR=$(dirname "$0") + +set -a + +AGENT_PATH="$SCRIPT_DIR" + +set +a + + +if [ -f "$SCRIPT_DIR/common.sh" ]; then + source "$SCRIPT_DIR/common.sh" +else + echo "Error: common.sh not found in $SCRIPT_DIR" + exit 1 +fi + +_check_required_env_vars "AGENT_PATH" + +function install_bb64() { + # check curl installed + if ! command -v curl &> /dev/null; then + _die "Curl is not installed. Curl is required for agent installation." + fi + + curl -fsSL "https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh" | bash -s -- "$AGENT_PATH" "$(id -u $USER):$(id -g $USER)" + + # test result code from curl + if [ $? -ne 0 ]; then + _die "Failed to install bb64. Curl returned non-zero exit code." + fi + + # test if bb64 is installed + "$AGENT_PATH/bb64" -v + if [ $? -ne 0 ]; then + _die "bb64 did not install correctly." + fi + + echo "bb64 installed successfully." + return 0; +} + +#------------------------------------------------------------------------- + +echo "Installing dropshell agent..." + +install_bb64 + + + +#------------------------------------------------------------------------- + +echo "Running remote agent self-test..." + + +#------------------------------------------------------------------------- + +echo "Completed remote agent self-test." + + +#------------------------------------------------------------------------- + +echo "Completed dropshell agent installation." + +#------------------------------------------------------------------------- + +exit 0 diff --git a/source/agent/common.sh b/source/agent-remote/common.sh similarity index 100% rename from source/agent/common.sh rename to source/agent-remote/common.sh diff --git a/source/agent/datacommands.sh b/source/agent-remote/datacommands.sh similarity index 100% rename from source/agent/datacommands.sh rename to source/agent-remote/datacommands.sh diff --git a/source/agent/selftest.sh b/source/agent/selftest.sh deleted file mode 100755 index 594d586..0000000 --- a/source/agent/selftest.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -echo "Running remote agent self-test..." - -echo "Completed remote agent self-test." - -exit 0 diff --git a/source/make_createagent.sh b/source/make_createagent.sh index c12b1d3..aca5f0b 100755 --- a/source/make_createagent.sh +++ b/source/make_createagent.sh @@ -14,6 +14,5 @@ if ! command -v dehydrate &> /dev/null; then curl -fsSL https://gitea.jde.nz/public/dehydrate/releases/download/latest/install.sh | bash fi -SCRIPT_DIR=$(dirname "$0") - -dehydrate "${SCRIPT_DIR}/agent" "${SCRIPT_DIR}/src/autogen" +dehydrate "${SCRIPT_DIR}/agent-remote" "${SCRIPT_DIR}/src/autogen" +dehydrate "${SCRIPT_DIR}/agent-local" "${SCRIPT_DIR}/src/autogen" diff --git a/source/src/autogen/_agent-local.cpp b/source/src/autogen/_agent-local.cpp new file mode 100644 index 0000000..c0f3abe --- /dev/null +++ b/source/src/autogen/_agent-local.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include + +/* + + THIS FILE IS AUTO-GENERATED BY DEHYDRATE. + DO NOT EDIT THIS FILE. + +*/ + + +#include "_agent-local.hpp" +namespace recreate_agent_local { + + +// Tiny dependency-free FNV-1a 64-bit hash +static uint64_t fnv1a_64(const void* data, size_t len) { + const uint8_t* p = static_cast(data); + uint64_t h = 0xcbf29ce484222325ULL; + for (size_t i = 0; i < len; ++i) + h = (h ^ p[i]) * 0x100000001b3ULL; + return h; +} + + +// Base64 decoding function - no dependencies +static void base64_decode(const char* encoded_data, size_t encoded_len, unsigned char* output, size_t* output_len) { + const char* base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t out_pos = 0; + int val = 0, valb = -8; + + for (size_t i = 0; i < encoded_len; i++) { + char c = encoded_data[i]; + if (c == '=') break; + + // Find position in base64_chars + const char* pos = strchr(base64_chars, c); + if (pos == nullptr) continue; // Skip invalid characters + + val = (val << 6) + static_cast(pos - base64_chars); + valb += 6; + if (valb >= 0) { + output[out_pos++] = static_cast((val >> valb) & 0xFF); + valb -= 8; + } + } + + *output_len = out_pos; +} + +// Utility function to recreate a file with proper permissions +static bool _recreate_file_(const std::filesystem::path& outpath, uint64_t file_hash, std::filesystem::perms file_perms, const unsigned char* filedata, size_t filedata_len) { + namespace fs = std::filesystem; + bool needs_write = false; + + // Check if file exists and has correct hash + if (fs::exists(outpath)) { + // Check content hash + std::ifstream in(outpath, std::ios::binary); + std::ostringstream oss; + oss << in.rdbuf(); + std::string data = oss.str(); + uint64_t existing_hash = fnv1a_64(data.data(), data.size()); + needs_write = existing_hash != file_hash; + } else { + needs_write = true; // File doesn't exist, need to create it + } + + bool needs_permission_update = true; + if (!needs_write) { // we always update permissions if the file is written or changed. Othewise we check. + fs::perms current_perms = fs::status(outpath).permissions(); + needs_permission_update = current_perms != file_perms; + } + + if (needs_write) { + fs::create_directories(outpath.parent_path()); + std::ofstream out(outpath, std::ios::binary); + out.write(reinterpret_cast(filedata), filedata_len); + out.close(); + // Set the file permissions + fs::permissions(outpath, file_perms); + + if (!fs::exists(outpath)) { + std::cout << "[dehydrate] " << outpath.filename() << ": created\n"; + } else { + std::cout << "[dehydrate] " << outpath.filename() << ": updated (hash changed)\n"; + } + return true; + } + + if (needs_permission_update) { + // Update only permissions + fs::permissions(outpath, file_perms); + std::cout << "[dehydrate] " << outpath.filename() << ": updated (permissions changed)\n"; + return true; + } + + return false; +} + +bool recreate_tree(std::string destination_folder) { + namespace fs = std::filesystem; + bool any_written = false; + { + // File: agent-install.sh + fs::path outpath = fs::path(destination_folder) / "agent-install.sh"; + static const char filedata_base64[] = "IyEvYmluL2Jhc2gKClNDUklQVF9ESVI9JChkaXJuYW1lICIkMCIpCgplY2hvICJJbnN0YWxsaW5n"\ + "IGRyb3BzaGVsbCBob3N0IGFnZW50IG9uIHRoaXMgY29tcHV0ZXIuLi4iCgoKIyBQcmludHMgYW4g"\ + "ZXJyb3IgbWVzc2FnZSBpbiByZWQgYW5kIGV4aXRzIHdpdGggc3RhdHVzIGNvZGUgMS4KX2RpZSgp"\ + "IHsKICAgIGVjaG8gLWUgIkVycm9yOiAkMSIKICAgIGV4aXQgMQp9CgoKIyBDaGVja3MgaWYgbGlz"\ + "dGVkIGVudmlyb25tZW50IHZhcmlhYmxlcyBhcmUgc2V0OyBjYWxscyBfZGllKCkgaWYgYW55IGFy"\ + "ZSBtaXNzaW5nLgpfY2hlY2tfcmVxdWlyZWRfZW52X3ZhcnMoKSB7CiAgICBsb2NhbCByZXF1aXJl"\ + "ZF92YXJzPSgiJEAiKQogICAgZm9yIHZhciBpbiAiJHtyZXF1aXJlZF92YXJzW0BdfSI7IGRvCiAg"\ + "ICAgICAgaWYgWyAteiAiJHshdmFyfSIgXTsgdGhlbgogICAgICAgICAgICBfZGllICJSZXF1aXJl"\ + "ZCBlbnZpcm9ubWVudCB2YXJpYWJsZSAkdmFyIGlzIG5vdCBzZXQiCiAgICAgICAgZmkKICAgIGRv"\ + "bmUKfQoKIyBDaGVja3MgaWYgRG9ja2VyIGlzIGluc3RhbGxlZCwgcnVubmluZywgYW5kIHVzZXIg"\ + "aGFzIHBlcm1pc3Npb24uIFJldHVybnMgMSBvbiBmYWlsdXJlLgpfY2hlY2tfZG9ja2VyX2luc3Rh"\ + "bGxlZCgpIHsKICAgIGlmICEgY29tbWFuZCAtdiBkb2NrZXIgJj4gL2Rldi9udWxsOyB0aGVuCiAg"\ + "ICAgICAgZWNobyAiRG9ja2VyIGlzIG5vdCBpbnN0YWxsZWQiCiAgICAgICAgcmV0dXJuIDEKICAg"\ + "IGZpCgogICAgIyBjaGVjayBpZiBkb2NrZXIgZGFlbW9uIGlzIHJ1bm5pbmcKICAgIGlmICEgZG9j"\ + "a2VyIGluZm8gJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAgZWNobyAiRG9ja2VyIGRhZW1vbiBp"\ + "cyBub3QgcnVubmluZyIKICAgICAgICByZXR1cm4gMQogICAgZmkKCiAgICAjIGNoZWNrIGlmIHVz"\ + "ZXIgaGFzIHBlcm1pc3Npb24gdG8gcnVuIGRvY2tlcgogICAgaWYgISBkb2NrZXIgcnVuIC0tcm0g"\ + "aGVsbG8td29ybGQgJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAgZWNobyAiVXNlciBkb2VzIG5v"\ + "dCBoYXZlIHBlcm1pc3Npb24gdG8gcnVuIGRvY2tlciIKICAgICAgICByZXR1cm4gMQogICAgZmkK"\ + "CiAgICByZXR1cm4gMAp9CgpmdW5jdGlvbiBpbnN0YWxsX2JiNjQoKSB7ICAgIAogICAgIyBjaGVj"\ + "ayBjdXJsIGluc3RhbGxlZAogICAgaWYgISBjb21tYW5kIC12IGN1cmwgJj4gL2Rldi9udWxsOyB0"\ + "aGVuCiAgICAgICAgX2RpZSAiQ3VybCBpcyBub3QgaW5zdGFsbGVkLiBDdXJsIGlzIHJlcXVpcmVk"\ + "IGZvciBhZ2VudCBpbnN0YWxsYXRpb24uIgogICAgZmkKCiAgICBjdXJsIC1mc1NMICJodHRwczov"\ + "L2dpdGVhLmpkZS5uei9wdWJsaWMvYmI2NC9yZWxlYXNlcy9kb3dubG9hZC9sYXRlc3QvaW5zdGFs"\ + "bC5zaCIgfCBiYXNoIC1zIC0tICIkQUdFTlRfUEFUSCIgIiQoaWQgLXUgJFVTRVIpOiQoaWQgLWcg"\ + "JFVTRVIpIgoKICAgICMgdGVzdCByZXN1bHQgY29kZSBmcm9tIGN1cmwKICAgIGlmIFsgJD8gLW5l"\ + "IDAgXTsgdGhlbgogICAgICAgIF9kaWUgIkZhaWxlZCB0byBpbnN0YWxsIGJiNjQuIEN1cmwgcmV0"\ + "dXJuZWQgbm9uLXplcm8gZXhpdCBjb2RlLiIKICAgIGZpCgogICAgIyB0ZXN0IGlmIGJiNjQgaXMg"\ + "aW5zdGFsbGVkCiAgICAiJEFHRU5UX1BBVEgvYmI2NCIgLXYKICAgIGlmIFsgJD8gLW5lIDAgXTsg"\ + "dGhlbgogICAgICAgIF9kaWUgImJiNjQgZGlkIG5vdCBpbnN0YWxsIGNvcnJlY3RseS4iCiAgICBm"\ + "aQoKICAgIGVjaG8gImJiNjQgaW5zdGFsbGVkIHN1Y2Nlc3NmdWxseS4iCiAgICByZXR1cm4gMDsK"\ + "fQoKCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\ + "LS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpzZXQgLWEKQUdFTlRfUEFUSD0iJFNDUklQVF9ESVIiCnNl"\ + "dCArYQoKX2NoZWNrX3JlcXVpcmVkX2Vudl92YXJzICJBR0VOVF9QQVRIIgplY2hvICJJbnN0YWxs"\ + "aW5nIGhvc3QgYWdlbnQgaW50byAkQUdFTlRfUEFUSCIKCl9jaGVja19kb2NrZXJfaW5zdGFsbGVk"\ + "IHx8IF9kaWUgIkRvY2tlciBpcyByZXF1aXJlZC4iCgppbnN0YWxsX2JiNjQKCg=="; + + // Decode Base64 data + size_t decoded_size = (strlen(filedata_base64) * 3) / 4; + unsigned char* decoded_data = new unsigned char[decoded_size]; + size_t actual_size; + base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size); + + bool file_written = _recreate_file_(outpath, 14766786204011647584ULL, std::filesystem::perms(493), decoded_data, actual_size); + delete[] decoded_data; + any_written = any_written || file_written; + } + return any_written; +} +} diff --git a/source/src/autogen/_agent.hpp b/source/src/autogen/_agent-local.hpp similarity index 83% rename from source/src/autogen/_agent.hpp rename to source/src/autogen/_agent-local.hpp index 082aa4a..1e52976 100644 --- a/source/src/autogen/_agent.hpp +++ b/source/src/autogen/_agent-local.hpp @@ -10,6 +10,6 @@ #include -namespace recreate_agent { +namespace recreate_agent_local { bool recreate_tree(std::string destination_folder); } diff --git a/source/src/autogen/_agent.cpp b/source/src/autogen/_agent-remote.cpp similarity index 91% rename from source/src/autogen/_agent.cpp rename to source/src/autogen/_agent-remote.cpp index c1b0825..e371ff7 100644 --- a/source/src/autogen/_agent.cpp +++ b/source/src/autogen/_agent-remote.cpp @@ -12,8 +12,8 @@ */ -#include "_agent.hpp" -namespace recreate_agent { +#include "_agent-remote.hpp" +namespace recreate_agent_remote { // Tiny dependency-free FNV-1a 64-bit hash @@ -105,22 +105,6 @@ static bool _recreate_file_(const std::filesystem::path& outpath, uint64_t file_ bool recreate_tree(std::string destination_folder) { namespace fs = std::filesystem; bool any_written = false; - { - // File: selftest.sh - fs::path outpath = fs::path(destination_folder) / "selftest.sh"; - static const char filedata_base64[] = "IyEvYmluL2Jhc2gKCmVjaG8gIlJ1bm5pbmcgcmVtb3RlIGFnZW50IHNlbGYtdGVzdC4uLiIKCmVj"\ - "aG8gIkNvbXBsZXRlZCByZW1vdGUgYWdlbnQgc2VsZi10ZXN0LiIKCmV4aXQgMAo="; - - // Decode Base64 data - size_t decoded_size = (strlen(filedata_base64) * 3) / 4; - unsigned char* decoded_data = new unsigned char[decoded_size]; - size_t actual_size; - base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size); - - bool file_written = _recreate_file_(outpath, 11594895391899191874ULL, std::filesystem::perms(493), decoded_data, actual_size); - delete[] decoded_data; - any_written = any_written || file_written; - } { // File: datacommands.sh fs::path outpath = fs::path(destination_folder) / "datacommands.sh"; @@ -248,6 +232,48 @@ bool recreate_tree(std::string destination_folder) { delete[] decoded_data; any_written = any_written || file_written; } + { + // File: agent-install.sh + fs::path outpath = fs::path(destination_folder) / "agent-install.sh"; + static const char filedata_base64[] = "IyEvYmluL2Jhc2gKCiMgVGhpcyBzY3JpcHQgaXMgdXNlZCB0byBpbnN0YWxsIHRoZSBkcm9wc2hl"\ + "bGwgYWdlbnQgb24gYSByZW1vdGUgc2VydmVyLgoKU0NSSVBUX0RJUj0kKGRpcm5hbWUgIiQwIikK"\ + "CnNldCAtYQoKQUdFTlRfUEFUSD0iJFNDUklQVF9ESVIiCgpzZXQgK2EKCgppZiBbIC1mICIkU0NS"\ + "SVBUX0RJUi9jb21tb24uc2giIF07IHRoZW4KICAgIHNvdXJjZSAiJFNDUklQVF9ESVIvY29tbW9u"\ + "LnNoIgplbHNlCiAgICBlY2hvICJFcnJvcjogY29tbW9uLnNoIG5vdCBmb3VuZCBpbiAkU0NSSVBU"\ + "X0RJUiIKICAgIGV4aXQgMQpmaQoKX2NoZWNrX3JlcXVpcmVkX2Vudl92YXJzICJBR0VOVF9QQVRI"\ + "IgoKZnVuY3Rpb24gaW5zdGFsbF9iYjY0KCkgeyAgICAKICAgICMgY2hlY2sgY3VybCBpbnN0YWxs"\ + "ZWQKICAgIGlmICEgY29tbWFuZCAtdiBjdXJsICY+IC9kZXYvbnVsbDsgdGhlbgogICAgICAgIF9k"\ + "aWUgIkN1cmwgaXMgbm90IGluc3RhbGxlZC4gQ3VybCBpcyByZXF1aXJlZCBmb3IgYWdlbnQgaW5z"\ + "dGFsbGF0aW9uLiIKICAgIGZpCgogICAgY3VybCAtZnNTTCAiaHR0cHM6Ly9naXRlYS5qZGUubnov"\ + "cHVibGljL2JiNjQvcmVsZWFzZXMvZG93bmxvYWQvbGF0ZXN0L2luc3RhbGwuc2giIHwgYmFzaCAt"\ + "cyAtLSAiJEFHRU5UX1BBVEgiICIkKGlkIC11ICRVU0VSKTokKGlkIC1nICRVU0VSKSIKCiAgICAj"\ + "IHRlc3QgcmVzdWx0IGNvZGUgZnJvbSBjdXJsCiAgICBpZiBbICQ/IC1uZSAwIF07IHRoZW4KICAg"\ + "ICAgICBfZGllICJGYWlsZWQgdG8gaW5zdGFsbCBiYjY0LiBDdXJsIHJldHVybmVkIG5vbi16ZXJv"\ + "IGV4aXQgY29kZS4iCiAgICBmaQoKICAgICMgdGVzdCBpZiBiYjY0IGlzIGluc3RhbGxlZAogICAg"\ + "IiRBR0VOVF9QQVRIL2JiNjQiIC12CiAgICBpZiBbICQ/IC1uZSAwIF07IHRoZW4KICAgICAgICBf"\ + "ZGllICJiYjY0IGRpZCBub3QgaW5zdGFsbCBjb3JyZWN0bHkuIgogICAgZmkKCiAgICBlY2hvICJi"\ + "YjY0IGluc3RhbGxlZCBzdWNjZXNzZnVsbHkuIgogICAgcmV0dXJuIDA7Cn0KCiMtLS0tLS0tLS0t"\ + "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\ + "LS0tLS0tCgplY2hvICJJbnN0YWxsaW5nIGRyb3BzaGVsbCBhZ2VudC4uLiIKCmluc3RhbGxfYmI2"\ + "NAoKCgojLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\ + "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKZWNobyAiUnVubmluZyByZW1vdGUgYWdlbnQgc2VsZi10"\ + "ZXN0Li4uIgoKCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\ + "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgplY2hvICJDb21wbGV0ZWQgcmVtb3RlIGFnZW50"\ + "IHNlbGYtdGVzdC4iCgoKIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\ + "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCmVjaG8gIkNvbXBsZXRlZCBkcm9wc2hl"\ + "bGwgYWdlbnQgaW5zdGFsbGF0aW9uLiIKCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\ + "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpleGl0IDAK"; + + // Decode Base64 data + size_t decoded_size = (strlen(filedata_base64) * 3) / 4; + unsigned char* decoded_data = new unsigned char[decoded_size]; + size_t actual_size; + base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size); + + bool file_written = _recreate_file_(outpath, 8417440848759774142ULL, std::filesystem::perms(493), decoded_data, actual_size); + delete[] decoded_data; + any_written = any_written || file_written; + } { // File: _allservicesstatus.sh fs::path outpath = fs::path(destination_folder) / "_allservicesstatus.sh"; diff --git a/source/src/autogen/_agent-remote.hpp b/source/src/autogen/_agent-remote.hpp new file mode 100644 index 0000000..2c0d46f --- /dev/null +++ b/source/src/autogen/_agent-remote.hpp @@ -0,0 +1,15 @@ + +#pragma once + +/* + + THIS FILE IS AUTO-GENERATED BY DEHYDRATE. + DO NOT EDIT THIS FILE. + +*/ + + +#include +namespace recreate_agent_remote { + bool recreate_tree(std::string destination_folder); +} diff --git a/source/src/commands/install.cpp b/source/src/commands/install.cpp index 9f5e84d..559b681 100644 --- a/source/src/commands/install.cpp +++ b/source/src/commands/install.cpp @@ -5,7 +5,8 @@ #include "templates.hpp" #include "shared_commands.hpp" #include "utils/hash.hpp" -#include "autogen/_agent.hpp" +#include "autogen/_agent-local.hpp" +#include "autogen/_agent-remote.hpp" #include "services.hpp" #include "utils/output.hpp" @@ -248,24 +249,15 @@ namespace dropshell std::filesystem::create_directories(p); } - // download bb64 for the host architecture. - if (!std::filesystem::exists(localpath::agent() + "bb64")) - { - std::string cmd = "cd " + localpath::agent() + " && curl -fsSL -o bb64 https://gitea.jde.nz/public/bb64/releases/download/latest/bb64.amd64 && chmod a+x bb64"; - int ret = system(cmd.c_str()); - if (EXITSTATUSCHECK(ret)) - info << "Downloaded local bb64 to " << localpath::agent() << std::endl; - else - error << "Failed to download local bb64 to " << localpath::agent() << std::endl; - } - else - { - info << "Updating local bb64..." << std::endl; - system((localpath::agent() + "bb64 -u").c_str()); // update. - } + // create the agent-local directory. + recreate_agent_local::recreate_tree(localpath::agent()); + // run the local agent installer. + execute_local_command(localpath::agent(), "agent-install.sh",{}, nullptr, cMode::Defaults | cMode::NoBB64); + + // create the agent-remote directory. info << "Creating local files to copy to remote agents..." << std::endl; - recreate_agent::recreate_tree(localpath::files_for_remote_agent()); + recreate_agent_remote::recreate_tree(localpath::files_for_remote_agent()); return 0; } @@ -295,23 +287,9 @@ namespace dropshell shared_commands::rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false); info << "done." << std::endl; - // add in bb64. We can't use execute_remote_command() here, as that relies on bb64 which we're installing! - info << "Installing bb64 on " << server << "..." << std::endl - << std::flush; - - std::string remote_cmd = - "ssh -p " + server_env.get_SSH_INFO().port + " " + server_env.get_SSH_INFO().user + "@" + server_env.get_SSH_INFO().host + - " 'mkdir -p " + quote(agent_path) + " && curl -fsSL \"https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh\" | bash -s -- " + - quote(agent_path) + " " + quote("$(id -u " + server_env.get_SSH_USER() + "):$(id -g " + server_env.get_SSH_USER() + ")") + "'"; - - // std::cout << "Executing: " << remote_cmd << std::endl; - if (!execute_local_command("", remote_cmd, {}, nullptr, cMode::Silent)) - error << "Failed to download bb64 to " << agent_path << " on remote server." << std::endl; - else - debug << "Downloaded bb64 to " << agent_path << " on remote server." << std::endl; - - // run the self-test. - bool okay = execute_ssh_command(server_env.get_SSH_INFO(), sCommand(agent_path, "./selftest.sh", {}), cMode::Defaults, nullptr); + // run the agent installer. Can't use BB64 yet, as we're installing it on the remote server. + + bool okay = execute_ssh_command(server_env.get_SSH_INFO(), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr); if (!okay) { error << "ERROR: Failed to install remote agent on " << server << std::endl; diff --git a/source/src/commands/restoredata.cpp b/source/src/commands/restoredata.cpp index e43ba43..8a1c7d3 100644 --- a/source/src/commands/restoredata.cpp +++ b/source/src/commands/restoredata.cpp @@ -127,6 +127,16 @@ namespace dropshell ASSERT(backup_details.has_value() && backup_details->is_valid(), "Invalid backup file."); + debug << "Backup details: " << std::endl; + debug << " Backup filename: " << backup_details->get_filename() << std::endl; + debug << " Backup template: " << backup_details->get_template_name() << std::endl; + debug << " Backup taken from server: " << backup_details->get_server() << std::endl; + debug << " Backup taken from service: " << backup_details->get_service() << std::endl; + debug << " " << std::endl; + debug << "Restoring to:" << std::endl; + debug << " Server: " << server << std::endl; + debug << " Service: " << service << std::endl; + std::string local_backups_dir = gConfig().get_local_backup_path(); if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) { @@ -143,14 +153,11 @@ namespace dropshell if (backup_details->get_template_name() != service_info.template_name) { error << "Error: Backup template does not match service template. Can't restore." << std::endl; - debug << "Backup template: " << backup_details->get_template_name() << std::endl; - debug << "Service template: " << service_info.template_name << std::endl; - debug << "Backup file: " << local_backup_file_path << std::endl; + info << "Backup template: " << backup_details->get_template_name() << std::endl; + info << "Service template: " << service_info.template_name << std::endl; return 1; } - info << "Restoring " << backup_details->get_datetime() << " backup of " << backup_details->get_service() << " taken from " << backup_details->get_server() << ", onto " << server << "/" << service << std::endl; - info << std::endl; warning << "*** ALL DATA FOR " << server << "/" << service << " WILL BE OVERWRITTEN! ***" << std::endl; // run the restore script @@ -160,9 +167,9 @@ namespace dropshell info << "1) Backing up old service... " << std::endl; if (!shared_commands::backupdata_service(server, service)) { - error << "Error: Backup failed, restore aborted." << std::endl; - error << "You can try using dropshell install " << server << " " << service << " to install the service afresh." << std::endl; - error << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl; + error << "Backup failed, restore aborted." << std::endl; + info << "You can try using dropshell install " << server << " " << service << " to install the service afresh." << std::endl; + info << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl; return 1; } info << "Backup complete." << std::endl; diff --git a/source/src/commands/shared_commands.cpp b/source/src/commands/shared_commands.cpp index 6863b9d..a17718b 100644 --- a/source/src/commands/shared_commands.cpp +++ b/source/src/commands/shared_commands.cpp @@ -121,7 +121,7 @@ namespace dropshell 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::CaptureOutput | cMode::Silent, + cMode::Silent, &output)) return status; diff --git a/source/src/server_env_manager.cpp b/source/src/server_env_manager.cpp index 0653f16..9cd5303 100644 --- a/source/src/server_env_manager.cpp +++ b/source/src/server_env_manager.cpp @@ -218,8 +218,7 @@ 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); - cMode mode = cMode::CaptureOutput; - return execute_ssh_command(get_SSH_INFO(), scommand.value(), mode, &output); + return execute_ssh_command(get_SSH_INFO(), scommand.value(), cMode::Defaults, &output); } diff --git a/source/src/utils/execute.cpp b/source/src/utils/execute.cpp index f9b5602..998ad1a 100644 --- a/source/src/utils/execute.cpp +++ b/source/src/utils/execute.cpp @@ -22,7 +22,6 @@ namespace dropshell return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute. } - // ---------------------------------------------------------------------------------------------------------- // execute_local_command_interactive // ---------------------------------------------------------------------------------------------------------- @@ -30,7 +29,7 @@ namespace dropshell { if (command.get_command_to_run().empty()) return false; - std::string full_command = command.construct_cmd(localpath::agent()); // Get the command string + std::string full_command = command.construct_cmd(localpath::agent()+"/bb64"); // Get the command string pid_t pid = fork(); @@ -62,7 +61,7 @@ namespace dropshell class fancypinter { - public: + public: fancypinter(sColour startColour) : startColour_(startColour), currentColour_(startColour) {} void print_chunk(std::string chunk) @@ -82,18 +81,22 @@ namespace dropshell else if (chunk.find("info") == 0) currentColour_ = sColour::INFO; else - currentColour_ = startColour_; + currentColour_ = startColour_; } colourstream(currentColour_) << chunk; - newline_ = (chunk[chunk.size()-1] == '\n'); + newline_ = (chunk[chunk.size() - 1] == '\n'); } - void print(const std::string& buffer) { + void print(const std::string &buffer) + { size_t start = 0; - while (start < buffer.size()) { + while (start < buffer.size()) + { size_t newline_pos = buffer.find('\n', start); - if (newline_pos == std::string::npos) { - if (start < buffer.size()) { + if (newline_pos == std::string::npos) + { + if (start < buffer.size()) + { print_chunk(buffer.substr(start)); } break; @@ -103,32 +106,36 @@ namespace dropshell } } - private: + private: bool newline_ = true; sColour startColour_; sColour currentColour_; }; - bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map &env_vars, std::string *output, cMode mode) { sCommand command(directory_to_run_in, command_to_run, env_vars); if (hasFlag(mode, cMode::Interactive)) { - ASSERT(!hasFlag(mode, cMode::CaptureOutput), "Interactive mode and capture output mode cannot be used together"); ASSERT(output == nullptr, "Interactive mode and an output string cannot be used together"); return execute_local_command_interactive(command); } - if (command.get_command_to_run().empty()) return false; bool silent = hasFlag(mode, cMode::Silent); - std::string full_cmd = command.construct_cmd(localpath::agent()) + (hasFlag(mode, cMode::CaptureOutput) ? " 2>&1" : ""); // capture both stdout and stderr + std::string full_cmd; + if (!hasFlag(mode, cMode::NoBB64)) + full_cmd = command.construct_cmd(localpath::agent()+"/bb64"); + else + full_cmd = command.construct_cmd(""); + + if (output != nullptr) + full_cmd += " 2>&1"; // capture both stdout and stderr FILE *pipe = popen(full_cmd.c_str(), "r"); if (!pipe) @@ -140,7 +147,7 @@ namespace dropshell while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { if (output != nullptr) - (*output) += buffer; + (*output) += buffer; if (!silent) fancyprint.print(buffer); @@ -157,30 +164,27 @@ namespace dropshell if (remote_command.get_command_to_run().empty()) return false; - ASSERT(!(hasFlag(mode, cMode::CaptureOutput) && output == nullptr), "Capture output mode must be used with an output string"); - std::stringstream ssh_cmd; ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "") << ssh_info.user << "@" << ssh_info.host; - std::string remote_agent_path = remotepath::agent(ssh_info.server_ID); + std::string remote_bb64_path; + + if (!hasFlag(mode, cMode::NoBB64)) + remote_bb64_path = remotepath::agent(ssh_info.server_ID) + "/bb64"; bool rval = execute_local_command( - "", // directory to run in - ssh_cmd.str() + " " + remote_command.construct_cmd(remote_agent_path), // local command to run - {}, // environment variables - output, // output string - mode // mode + "", // local directory to run in + ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path), // local command to run + {}, // environment variables + output, // output string + mode // mode ); if (!rval && !hasFlag(mode, cMode::Silent)) { - std::cerr << std::endl - << std::endl; - std::cerr << "Error: Failed to execute ssh command:" << std::endl; - std::cerr << "\033[90m" << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_agent_path) << "\033[0m" << std::endl; - std::cerr << std::endl - << std::endl; + error << "Error: Failed to execute ssh command:" << std::endl; + debug << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path) << std::endl; } return rval; } @@ -188,19 +192,19 @@ namespace dropshell // ---------------------------------------------------------------------------------------------------------- // makesafecmd // ---------------------------------------------------------------------------------------------------------- - std::string sCommand::makesafecmd(std::string agent_path, const std::string &command) const + std::string sCommand::makesafecmd(std::string bb64path, const std::string &command) const { if (command.empty()) return ""; std::string encoded = base64_encode(dequote(trim(command))); - std::string commandstr = agent_path + "/bb64 " + encoded; + std::string commandstr = bb64path + " " + encoded; return commandstr; } // ---------------------------------------------------------------------------------------------------------- // construct_cmd // ---------------------------------------------------------------------------------------------------------- - std::string sCommand::construct_cmd(std::string agent_path) const + std::string sCommand::construct_cmd(std::string bb64path) const { if (mCmd.empty()) return ""; @@ -208,17 +212,27 @@ namespace dropshell // need to construct to change directory and set environment variables std::string cmdstr; - if (!mDir.empty()) - cmdstr += "cd " + quote(mDir) + " && "; + if (!bb64path.empty()) + { + if (!mDir.empty()) + cmdstr += "cd " + quote(mDir) + " && "; - if (!mVars.empty()) - for (const auto &env_var : mVars) - cmdstr += env_var.first + "=" + quote(dequote(trim(env_var.second))) + " "; + if (!mVars.empty()) + for (const auto &env_var : mVars) + cmdstr += env_var.first + "=" + quote(dequote(trim(env_var.second))) + " "; - cmdstr += mCmd; + cmdstr += mCmd; - if (!agent_path.empty()) - cmdstr = makesafecmd(agent_path, cmdstr); + cmdstr = makesafecmd(bb64path, cmdstr); + } + else + { // raw! bootstrapping only. + ASSERT(mVars.empty(), "Bootstrapping command must not have environment variables"); + if (!mDir.empty()) + cmdstr += mDir + "/" + mCmd; + else + cmdstr += mCmd; + } return cmdstr; } diff --git a/source/src/utils/execute.hpp b/source/src/utils/execute.hpp index 353ab32..d8255a6 100644 --- a/source/src/utils/execute.hpp +++ b/source/src/utils/execute.hpp @@ -13,7 +13,7 @@ enum class cMode { Defaults = 0, Interactive = 1, Silent = 2, - CaptureOutput = 4 + NoBB64 = 4 }; inline cMode operator&(cMode lhs, cMode rhs) {return static_cast(static_cast(lhs) & static_cast(rhs));} @@ -52,10 +52,10 @@ class sCommand { bool empty() const { return mCmd.empty(); } - std::string construct_cmd(std::string agent_path) const; + std::string construct_cmd(std::string bb64path) const; private: - std::string makesafecmd(std::string agent_path, const std::string& command) const; + std::string makesafecmd(std::string bb64path, const std::string& command) const; private: std::string mDir;