Compare commits

...

29 Commits

Author SHA1 Message Date
60907e5e02 List tidy
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 11s
2025-05-24 17:00:43 +12:00
b3398582ca dropshell release 2025.0524.1314
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 12s
2025-05-24 13:14:51 +12:00
763293c7d0 dropshell release 2025.0524.1149
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 17s
2025-05-24 11:49:24 +12:00
0934179053 ...
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-24 11:15:23 +12:00
e1c631dc72 Fix list a bit
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-23 23:07:26 +12:00
b07704612b .
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-23 22:24:28 +12:00
462d215d5c Big refactor
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-23 22:06:37 +12:00
048345c636 Broken 2025-05-23 21:41:33 +12:00
94f77994f0 .
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 21:38:40 +12:00
b3a57f13dc dropshell release 2025.0521.2125
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 21:25:39 +12:00
270d6ef792 Tidying
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 20:51:05 +12:00
9063edb45f create-server
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 19:30:10 +12:00
fc6b310b89 dropshell release 2025.0521.1908
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 19:08:48 +12:00
7a710b525f dropshell release 2025.0521.1906
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-21 19:06:50 +12:00
1b16741288 .
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-19 23:09:43 +12:00
d8236a58df dropshell release 2025.0519.1727
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-19 17:28:01 +12:00
625de98890 Fix generated files in build, so they don't have to exist when the build starts.
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-19 11:24:42 +12:00
7c1b51a93c Don't include autogen in commits
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-19 11:19:47 +12:00
1be7af571f Remove autogen files. 2025-05-19 11:18:36 +12:00
1439ec2f79 Add dehydrate to build prereq's.
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-19 11:16:57 +12:00
6c3c35bf89 dropshell release 2025.0519.0021
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-19 00:21:34 +12:00
548ffea6f9 dropshell release 2025.0518.2308
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 23:08:44 +12:00
434a2bc6da dropshell release 2025.0518.2245
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 22:45:25 +12:00
cf8738aee9 dropshell release 2025.0518.2236
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 22:37:00 +12:00
d80820db15 Back to issue from pre-refactor!
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 20:23:15 +12:00
fb6974b51a Working on backup/restore.
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 20:19:47 +12:00
263edd9b50 dropshell release 2025.0518.1558
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 15:58:29 +12:00
e45afe460b Backups seem to be working
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
.
2025-05-18 15:43:19 +12:00
630a9fd19a Add backupdata
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 15:41:51 +12:00
54 changed files with 3510 additions and 2438 deletions

View File

@ -1,5 +1,5 @@
SSH_ADDRESS=localhost SSH_ADDRESS=localhost
SSH_PORT=22 SSH_PORT=22
SSH_USER=$(whoami) SSH_UNPRIVILEGED_USER=$(whoami)

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ out/
bin/ bin/
lib/ lib/
output/ output/
autogen/
# Compiled Object files # Compiled Object files
*.o *.o

View File

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.10)
project(dropshell VERSION 1.0.0 LANGUAGES CXX) project(dropshell VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Set default build type to Release if not specified # Set default build type to Release if not specified
@ -48,6 +49,19 @@ add_custom_target(run_createagent ALL
add_executable(dropshell ${SOURCES}) add_executable(dropshell ${SOURCES})
add_dependencies(dropshell run_createagent) add_dependencies(dropshell run_createagent)
# Mark the generated files as GENERATED so CMake knows they'll be created during build
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/src/autogen/_agent-remote.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/autogen/_agent-local.cpp
PROPERTIES GENERATED TRUE
)
# Explicitly add the generated agent files, as they might not be in the source directory when globbed at the start.
target_sources(dropshell PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/autogen/_agent-remote.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/autogen/_agent-local.cpp
)
# Set include directories # Set include directories
# build dir goes first so that we can use the generated version.hpp # build dir goes first so that we can use the generated version.hpp
target_include_directories(dropshell PRIVATE target_include_directories(dropshell PRIVATE

View File

@ -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_LOCAL_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_LOCAL_PATH/bb64" -v
if [ $? -ne 0 ]; then
_die "bb64 did not install correctly."
fi
echo "bb64 installed successfully."
return 0;
}
#-------------------------------------------------------------------------
set -a
AGENT_LOCAL_PATH="$SCRIPT_DIR"
set +a
_check_required_env_vars "AGENT_LOCAL_PATH"
echo "Installing host agent into $AGENT_LOCAL_PATH"
_check_docker_installed || _die "Docker is required."
install_bb64

View File

@ -0,0 +1 @@
TODO!

View File

@ -23,7 +23,6 @@ SCRIPT_DIR="$(dirname "$0")"
# // |-- service.env (actual service config) # // |-- service.env (actual service config)
# // |-- .template_info.env # // |-- .template_info.env
# // |-- template # // |-- template
# // |-- _default.env
# // |-- (script files) # // |-- (script files)
# // |-- config # // |-- config
# // |-- service.env (default service config) # // |-- service.env (default service config)
@ -66,7 +65,6 @@ function run_command() {
CURRENT_OUTPUT=$( CURRENT_OUTPUT=$(
set -a set -a
load_dotenv "${service_path}/template/_default.env"
load_dotenv "${service_path}/config/service.env" load_dotenv "${service_path}/config/service.env"
load_dotenv "${service_path}/config/.template_info.env" load_dotenv "${service_path}/config/.template_info.env"

View File

@ -0,0 +1,87 @@
#!/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() {
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
VER=$("$AGENT_PATH/bb64" -v)
if [ $? -ne 0 ]; then
_die "bb64 did not install correctly."
fi
echo "bb64 v$VER installed."
return 0;
}
#-------------------------------------------------------------------------
# Check pre-requisites
# Check if curl is installed
if ! command -v curl &> /dev/null; then
_die "Curl is not installed. Curl is required for agent installation."
fi
# check docker installation
if ! command -v docker &> /dev/null; then
echo "Docker is not installed. Docker is required for agent installation."
exit 1
fi
#-------------------------------------------------------------------------
echo "Installing dropshell agent..."
install_bb64
#-------------------------------------------------------------------------
# confirm we're in a good state.
required_files=(
"$AGENT_PATH/bb64"
"$AGENT_PATH/_allservicesstatus.sh"
"$AGENT_PATH/common.sh"
"$AGENT_PATH/datacommands.sh"
)
# check if all files exist
for file in "${required_files[@]}"; do
if [ ! -f "$file" ]; then
_die "$file does not exist. Please check the installation."
fi
done
#-------------------------------------------------------------------------
echo "Completed dropshell agent installation."
#-------------------------------------------------------------------------
exit 0

View File

@ -12,6 +12,10 @@ _autocommandrun_volume() {
case "$command" in case "$command" in
create) create)
if docker volume ls | grep -q ${volume_name}; then
echo "Volume ${volume_name} already exists - leaving unchanged"
return
fi
echo "Creating volume ${volume_name}" echo "Creating volume ${volume_name}"
docker volume create ${volume_name} docker volume create ${volume_name}
;; ;;
@ -39,6 +43,10 @@ _autocommandrun_path() {
case "$command" in case "$command" in
create) create)
if [ -d "${path}" ]; then
echo "Path ${path} already exists - unchanged"
return
fi
echo "Creating path ${path}" echo "Creating path ${path}"
mkdir -p ${path} mkdir -p ${path}
;; ;;
@ -47,7 +55,7 @@ _autocommandrun_path() {
local path_parent=$(dirname ${path}) local path_parent=$(dirname ${path})
local path_child=$(basename ${path}) local path_child=$(basename ${path})
if [ -d "${path_parent}/${path_child}" ]; then if [ -d "${path_parent}/${path_child}" ]; then
docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rf /volume/${path_child}" || echo "Failed to nuke path ${path}" docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to nuke path ${path}"
else else
echo "Path ${path} does not exist - nothing to nuke" echo "Path ${path} does not exist - nothing to nuke"
fi fi
@ -61,8 +69,14 @@ _autocommandrun_path() {
fi fi
;; ;;
restore) restore)
echo "Restoring path ${path}" if [ ! -f "${backup_folder}/backup.tgz" ]; then
echo "Backup file ${backup_folder}/backup.tgz does not exist - nothing to restore"
else
echo "Clearing existing data in path ${path}"
docker run --rm -v ${path}:/path debian bash -c "rm -rfv /path/{*,.*}"
echo "Restoring path ${path} from backup file ${backup_folder}/backup.tgz"
tar -xzvf ${backup_folder}/backup.tgz -C ${path} --strip-components=1 tar -xzvf ${backup_folder}/backup.tgz -C ${path} --strip-components=1
fi
;; ;;
esac esac
} }
@ -74,6 +88,12 @@ _autocommandrun_file() {
case "$command" in case "$command" in
create) create)
filepath_parent=$(dirname ${filepath})
filepath_child=$(basename ${filepath})
if [ ! -d "${filepath_parent}" ]; then
echo "Parent directory ${filepath_parent} of ${filepath_child} does not exist - creating"
mkdir -p ${filepath_parent}
fi
;; ;;
nuke) nuke)
rm -f ${filepath} rm -f ${filepath}
@ -91,7 +111,8 @@ _autocommandrun_file() {
restore) restore)
echo "Restoring file ${filepath}" echo "Restoring file ${filepath}"
local file_name=$(basename ${filepath}) local file_name=$(basename ${filepath})
cp ${backup_folder}/${file_name} ${filepath} rm -f ${filepath} || die "Unable to remove existing file ${filepath}, restore failed."
cp ${backup_folder}/${file_name} ${filepath} || die "Unable to copy file ${backup_folder}/${file_name} to ${filepath}, restore failed."
;; ;;
esac esac
} }

View File

@ -1,7 +0,0 @@
#!/bin/bash
echo "Running remote agent self-test..."
echo "Completed remote agent self-test."
exit 0

View File

@ -142,8 +142,12 @@ if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
fi fi
fi fi
# check if dehydrate command is installed
if ! command -v dehydrate &> /dev/null; then
curl -fsSL https://gitea.jde.nz/public/dehydrate/releases/download/latest/install.sh | bash
fi
dehydrate -u
print_status "All dependencies installed successfully!" print_status "All dependencies installed successfully!"

View File

@ -14,6 +14,7 @@ if ! command -v dehydrate &> /dev/null; then
curl -fsSL https://gitea.jde.nz/public/dehydrate/releases/download/latest/install.sh | bash curl -fsSL https://gitea.jde.nz/public/dehydrate/releases/download/latest/install.sh | bash
fi fi
SCRIPT_DIR=$(dirname "$0") mkdir -p "${SCRIPT_DIR}/src/autogen"
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"

View File

@ -59,7 +59,7 @@ bool autocomplete(const std::vector<std::string> &args)
{ {
auto servers = dropshell::get_configured_servers(); auto servers = dropshell::get_configured_servers();
for (const auto& server : servers) for (const auto& server : servers)
std::cout << server.name << std::endl; std::cout << server.get_server_name() << std::endl;
return true; return true;
} }

View File

@ -1,442 +0,0 @@
#include <fstream>
#include <filesystem>
#include <string>
#include <iostream>
#include <cstring>
/*
THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
DO NOT EDIT THIS FILE.
*/
#include "_agent.hpp"
namespace recreate_agent {
// 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<const uint8_t*>(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<int>(pos - base64_chars);
valb += 6;
if (valb >= 0) {
output[out_pos++] = static_cast<unsigned char>((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<const char*>(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: 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";
static const char filedata_base64[] = "IyEvYmluL2Jhc2gKCiMgVGhpcyBzY3JpcHQgY29udGFpbnMgdGhlIGNvbW1vbiBjb2RlIGZvciB0"\
"aGUgYXV0b2NvbW1hbmRzLgoKTVlJRD0kKGlkIC11KQpNWUdSUD0kKGlkIC1nKQoKX2F1dG9jb21t"\
"YW5kcnVuX3ZvbHVtZSgpIHsKICAgIGxvY2FsIGNvbW1hbmQ9IiQxIgogICAgbG9jYWwgdm9sdW1l"\
"X25hbWU9IiQyIgogICAgbG9jYWwgYmFja3VwX2ZvbGRlcj0iJDMiCgogICAgY2FzZSAiJGNvbW1h"\
"bmQiIGluCiAgICAgICAgY3JlYXRlKQogICAgICAgICAgICBlY2hvICJDcmVhdGluZyB2b2x1bWUg"\
"JHt2b2x1bWVfbmFtZX0iCiAgICAgICAgICAgIGRvY2tlciB2b2x1bWUgY3JlYXRlICR7dm9sdW1l"\
"X25hbWV9CiAgICAgICAgICAgIDs7CiAgICAgICAgbnVrZSkKICAgICAgICAgICAgZWNobyAiTnVr"\
"aW5nIHZvbHVtZSAke3ZvbHVtZV9uYW1lfSIKICAgICAgICAgICAgZG9ja2VyIHZvbHVtZSBybSAk"\
"e3ZvbHVtZV9uYW1lfQogICAgICAgICAgICA7OwogICAgICAgIGJhY2t1cCkKICAgICAgICAgICAg"\
"ZWNobyAiQmFja2luZyB1cCB2b2x1bWUgJHt2b2x1bWVfbmFtZX0iCiAgICAgICAgICAgIGRvY2tl"\
"ciBydW4gLS1ybSAtdiAke3ZvbHVtZV9uYW1lfTovdm9sdW1lIC12ICR7YmFja3VwX2ZvbGRlcn06"\
"L2JhY2t1cCBkZWJpYW4gYmFzaCAtYyAidGFyIC1jenZmIC9iYWNrdXAvYmFja3VwLnRneiAtQyAv"\
"dm9sdW1lIC4gJiYgY2hvd24gLVIgJE1ZSUQ6JE1ZR1JQIC9iYWNrdXAiCiAgICAgICAgICAgIDs7"\
"CiAgICAgICAgcmVzdG9yZSkKICAgICAgICAgICAgZWNobyAiUmVzdG9yaW5nIHZvbHVtZSAke3Zv"\
"bHVtZV9uYW1lfSIKICAgICAgICAgICAgZG9ja2VyIHZvbHVtZSBybSAke3ZvbHVtZV9uYW1lfQog"\
"ICAgICAgICAgICBkb2NrZXIgdm9sdW1lIGNyZWF0ZSAke3ZvbHVtZV9uYW1lfQogICAgICAgICAg"\
"ICBkb2NrZXIgcnVuIC0tcm0gLXYgJHt2b2x1bWVfbmFtZX06L3ZvbHVtZSAtdiAke2JhY2t1cF9m"\
"b2xkZXJ9Oi9iYWNrdXAgZGViaWFuIGJhc2ggLWMgInRhciAteHp2ZiAvYmFja3VwL2JhY2t1cC50"\
"Z3ogLUMgL3ZvbHVtZSAtLXN0cmlwLWNvbXBvbmVudHM9MSIKICAgICAgICAgICAgOzsKICAgIGVz"\
"YWMKfSAgIAoKX2F1dG9jb21tYW5kcnVuX3BhdGgoKSB7CiAgICBsb2NhbCBjb21tYW5kPSIkMSIK"\
"ICAgIGxvY2FsIHBhdGg9IiQyIgogICAgbG9jYWwgYmFja3VwX2ZvbGRlcj0iJDMiCgogICAgY2Fz"\
"ZSAiJGNvbW1hbmQiIGluCiAgICAgICAgY3JlYXRlKQogICAgICAgICAgICBlY2hvICJDcmVhdGlu"\
"ZyBwYXRoICR7cGF0aH0iCiAgICAgICAgICAgIG1rZGlyIC1wICR7cGF0aH0KICAgICAgICAgICAg"\
"OzsKICAgICAgICBudWtlKQogICAgICAgICAgICBlY2hvICJOdWtpbmcgcGF0aCAke3BhdGh9Igog"\
"ICAgICAgICAgICBsb2NhbCBwYXRoX3BhcmVudD0kKGRpcm5hbWUgJHtwYXRofSkKICAgICAgICAg"\
"ICAgbG9jYWwgcGF0aF9jaGlsZD0kKGJhc2VuYW1lICR7cGF0aH0pCiAgICAgICAgICAgIGlmIFsg"\
"LWQgIiR7cGF0aF9wYXJlbnR9LyR7cGF0aF9jaGlsZH0iIF07IHRoZW4KICAgICAgICAgICAgICAg"\
"IGRvY2tlciBydW4gLS1ybSAtdiAke3BhdGhfcGFyZW50fTovdm9sdW1lIGRlYmlhbiBiYXNoIC1j"\
"ICJybSAtcmYgL3ZvbHVtZS8ke3BhdGhfY2hpbGR9IiB8fCBlY2hvICJGYWlsZWQgdG8gbnVrZSBw"\
"YXRoICR7cGF0aH0iCiAgICAgICAgICAgIGVsc2UKICAgICAgICAgICAgICAgIGVjaG8gIlBhdGgg"\
"JHtwYXRofSBkb2VzIG5vdCBleGlzdCAtIG5vdGhpbmcgdG8gbnVrZSIKICAgICAgICAgICAgZmkK"\
"ICAgICAgICAgICAgOzsKICAgICAgICBiYWNrdXApCiAgICAgICAgICAgIGVjaG8gIkJhY2tpbmcg"\
"dXAgcGF0aCAke3BhdGh9IgogICAgICAgICAgICBpZiBbIC1kICIke3BhdGh9IiBdOyB0aGVuCiAg"\
"ICAgICAgICAgICAgICBkb2NrZXIgcnVuIC0tcm0gLXYgJHtwYXRofTovcGF0aCAtdiAke2JhY2t1"\
"cF9mb2xkZXJ9Oi9iYWNrdXAgZGViaWFuIGJhc2ggLWMgInRhciAtY3p2ZiAvYmFja3VwL2JhY2t1"\
"cC50Z3ogLUMgL3BhdGggLiAmJiBjaG93biAtUiAkTVlJRDokTVlHUlAgL2JhY2t1cCIKICAgICAg"\
"ICAgICAgZWxzZQogICAgICAgICAgICAgICAgZWNobyAiUGF0aCAke3BhdGh9IGRvZXMgbm90IGV4"\
"aXN0IC0gbm90aGluZyB0byBiYWNrdXAiCiAgICAgICAgICAgIGZpCiAgICAgICAgICAgIDs7CiAg"\
"ICAgICAgcmVzdG9yZSkKICAgICAgICAgICAgZWNobyAiUmVzdG9yaW5nIHBhdGggJHtwYXRofSIK"\
"ICAgICAgICAgICAgdGFyIC14enZmICR7YmFja3VwX2ZvbGRlcn0vYmFja3VwLnRneiAtQyAke3Bh"\
"dGh9IC0tc3RyaXAtY29tcG9uZW50cz0xCiAgICAgICAgICAgIDs7CiAgICBlc2FjCn0KCl9hdXRv"\
"Y29tbWFuZHJ1bl9maWxlKCkgewogICAgbG9jYWwgY29tbWFuZD0iJDEiCiAgICBsb2NhbCBmaWxl"\
"cGF0aD0iJDIiCiAgICBsb2NhbCBiYWNrdXBfZm9sZGVyPSIkMyIKCiAgICBjYXNlICIkY29tbWFu"\
"ZCIgaW4KICAgICAgICBjcmVhdGUpCiAgICAgICAgICAgIDs7CiAgICAgICAgbnVrZSkKICAgICAg"\
"ICAgICAgcm0gLWYgJHtmaWxlcGF0aH0KICAgICAgICAgICAgOzsKICAgICAgICBiYWNrdXApCiAg"\
"ICAgICAgICAgIGVjaG8gIkJhY2tpbmcgdXAgZmlsZSAke2ZpbGVwYXRofSIKICAgICAgICAgICAg"\
"bG9jYWwgZmlsZV9wYXJlbnQ9JChkaXJuYW1lICR7ZmlsZXBhdGh9KQogICAgICAgICAgICBsb2Nh"\
"bCBmaWxlX25hbWU9JChiYXNlbmFtZSAke2ZpbGVwYXRofSkKICAgICAgICAgICAgaWYgWyAtZiAi"\
"JHtmaWxlX3BhcmVudH0vJHtmaWxlX25hbWV9IiBdOyB0aGVuCiAgICAgICAgICAgICAgICBkb2Nr"\
"ZXIgcnVuIC0tcm0gLXYgJHtmaWxlX3BhcmVudH06L3ZvbHVtZSAtdiAke2JhY2t1cF9mb2xkZXJ9"\
"Oi9iYWNrdXAgZGViaWFuIGJhc2ggLWMgImNwIC92b2x1bWUvJHtmaWxlX25hbWV9IC9iYWNrdXAv"\
"JHtmaWxlX25hbWV9ICYmIGNob3duIC1SICRNWUlEOiRNWUdSUCAvYmFja3VwIgogICAgICAgICAg"\
"ICBlbHNlCiAgICAgICAgICAgICAgICBlY2hvICJGaWxlICR7ZmlsZXBhdGh9IGRvZXMgbm90IGV4"\
"aXN0IC0gbm90aGluZyB0byBiYWNrdXAiCiAgICAgICAgICAgIGZpCiAgICAgICAgICAgIDs7CiAg"\
"ICAgICAgcmVzdG9yZSkKICAgICAgICAgICAgZWNobyAiUmVzdG9yaW5nIGZpbGUgJHtmaWxlcGF0"\
"aH0iCiAgICAgICAgICAgIGxvY2FsIGZpbGVfbmFtZT0kKGJhc2VuYW1lICR7ZmlsZXBhdGh9KQog"\
"ICAgICAgICAgICBjcCAke2JhY2t1cF9mb2xkZXJ9LyR7ZmlsZV9uYW1lfSAke2ZpbGVwYXRofQog"\
"ICAgICAgICAgICA7OwogICAgZXNhYwp9CgpfYXV0b2NvbW1hbmRwYXJzZSgpIHsKICAgICMgZmly"\
"c3QgYXJndW1lbnQgaXMgdGhlIGNvbW1hbmQKICAgICMgaWYgdGhlIGNvbW1hbmQgaXMgYmFja3Vw"\
"IG9yIHJlc3RvcmUsIHRoZW4gdGhlIGxhc3QgdHdvIGFyZ3VtZW50cyBhcmUgdGhlIGJhY2t1cCBm"\
"aWxlIGFuZCB0aGUgdGVtcG9yYXJ5IHBhdGgKICAgICMgYWxsIG90aGVyIGFyZ3VtZW50cyBhcmUg"\
"b2YgZm9ybToKICAgICMga2V5PXZhbHVlCiAgICAjIHdoZXJlIGtleSBjYW4gYmUgb25lIG9mIHZv"\
"bHVtZSwgcGF0aCBvciBmaWxlLgogICAgIyB2YWx1ZSBpcyB0aGUgcGF0aCBvciB2b2x1bWUgbmFt"\
"ZS4KCiAgICAjIHdlIGl0ZXJhdGUgb3ZlciB0aGUga2V5PXZhbHVlIGFyZ3VtZW50cywgYW5kIGZv"\
"ciBlYWNoIHdlIGNhbGw6CiAgICAjICAgIGF1dG9ydW4gPGNvbW1hbmQ+IDxiYWNrdXBmaWxlPiA8"\
"a2V5PiA8dmFsdWU+CgogICAgbG9jYWwgY29tbWFuZD0iJDEiCiAgICBzaGlmdAoKICAgIGxvY2Fs"\
"IGJhY2t1cF90ZW1wX3BhdGg9IiQxIgogICAgc2hpZnQKCiAgICBlY2hvICJhdXRvY29tbWFuZHBh"\
"cnNlOiBjb21tYW5kPSRjb21tYW5kIGJhY2t1cF90ZW1wX3BhdGg9JGJhY2t1cF90ZW1wX3BhdGgi"\
"CgogICAgIyBFeHRyYWN0IHRoZSBiYWNrdXAgZmlsZSBhbmQgdGVtcCBwYXRoIChsYXN0IHR3byBh"\
"cmd1bWVudHMpCiAgICBsb2NhbCBhcmdzPSgiJEAiKQogICAgbG9jYWwgYXJnX2NvdW50PSR7I2Fy"\
"Z3NbQF19CiAgICAKICAgICMgUHJvY2VzcyBhbGwga2V5PXZhbHVlIHBhaXJzCiAgICBmb3IgKChp"\
"PTA7IGk8JGFyZ19jb3VudDsgaSsrKSk7IGRvCiAgICAgICAgbG9jYWwgcGFpcj0iJHthcmdzWyRp"\
"XX0iCiAgICAgICAgCiAgICAgICAgIyBTa2lwIGlmIG5vdCBpbiBrZXk9dmFsdWUgZm9ybWF0CiAg"\
"ICAgICAgaWYgW1sgIiRwYWlyIiAhPSAqIj0iKiBdXTsgdGhlbgogICAgICAgICAgICBjb250aW51"\
"ZQogICAgICAgIGZpCiAgICAgICAgCiAgICAgICAgbG9jYWwga2V5PSIke3BhaXIlJT0qfSIKICAg"\
"ICAgICBsb2NhbCB2YWx1ZT0iJHtwYWlyIyo9fSIKCiAgICAgICAgIyBjcmVhdGUgYmFja3VwIGZv"\
"bGRlciB1bmlxdWUgdG8ga2V5L3ZhbHVlLgogICAgICAgIGxvY2FsIGJmb2xkZXI9JChlY2hvICIk"\
"e2tleX1fJHt2YWx1ZX0iIHwgdHIgLWNkICdbOmFsbnVtOl1fLScpCiAgICAgICAgbG9jYWwgdGFy"\
"Z2V0cGF0aD0iJHtiYWNrdXBfdGVtcF9wYXRofS8ke2Jmb2xkZXJ9IgogICAgICAgIG1rZGlyIC1w"\
"ICR7dGFyZ2V0cGF0aH0KCiAgICAgICAgIyBLZXkgbXVzdCBiZSBvbmUgb2Ygdm9sdW1lLCBwYXRo"\
"IG9yIGZpbGUKICAgICAgICBjYXNlICIka2V5IiBpbgogICAgICAgICAgICB2b2x1bWUpCiAgICAg"\
"ICAgICAgICAgICBfYXV0b2NvbW1hbmRydW5fdm9sdW1lICIkY29tbWFuZCIgIiR2YWx1ZSIgIiR0"\
"YXJnZXRwYXRoIgogICAgICAgICAgICAgICAgOzsKICAgICAgICAgICAgcGF0aCkKICAgICAgICAg"\
"ICAgICAgIF9hdXRvY29tbWFuZHJ1bl9wYXRoICIkY29tbWFuZCIgIiR2YWx1ZSIgIiR0YXJnZXRw"\
"YXRoIgogICAgICAgICAgICAgICAgOzsKICAgICAgICAgICAgZmlsZSkKICAgICAgICAgICAgICAg"\
"IF9hdXRvY29tbWFuZHJ1bl9maWxlICIkY29tbWFuZCIgIiR2YWx1ZSIgIiR0YXJnZXRwYXRoIgog"\
"ICAgICAgICAgICAgICAgOzsKICAgICAgICAgICAgKikKICAgICAgICAgICAgICAgIF9kaWUgIlVu"\
"a25vd24ga2V5ICRrZXkgcGFzc2VkIHRvIGF1dG8ke2NvbW1hbmR9LiBXZSBvbmx5IHN1cHBvcnQg"\
"dm9sdW1lLCBwYXRoIGFuZCBmaWxlLiIKICAgICAgICAgICAgICAgIDs7CiAgICAgICAgZXNhYwog"\
"ICAgZG9uZQp9CgoKZGF0YWNyZWF0ZSgpIHsKICAgIF9hdXRvY29tbWFuZHBhcnNlIGNyZWF0ZSBu"\
"b25lICIkQCIKfQoKCmRhdGFudWtlKCkgewogICAgX2F1dG9jb21tYW5kcGFyc2UgbnVrZSBub25l"\
"ICIkQCIKfQoKZGF0YWJhY2t1cCgpIHsKICAgIF9jaGVja19yZXF1aXJlZF9lbnZfdmFycyAiQkFD"\
"S1VQX0ZJTEUiICJURU1QX0RJUiIKICAgIEJBQ0tVUF9URU1QX1BBVEg9IiRURU1QX0RJUi9iYWNr"\
"dXAiCgoKICAgIG1rZGlyIC1wICIkQkFDS1VQX1RFTVBfUEFUSCIKICAgIGVjaG8gIl9hdXRvY29t"\
"bWFuZHBhcnNlIFtiYWNrdXBdIFskQkFDS1VQX1RFTVBfUEFUSF0gWyRAXSIKICAgIF9hdXRvY29t"\
"bWFuZHBhcnNlIGJhY2t1cCAiJEJBQ0tVUF9URU1QX1BBVEgiICIkQCIKCiAgICB0YXIgemN2ZiAi"\
"JEJBQ0tVUF9GSUxFIiAtQyAiJEJBQ0tVUF9URU1QX1BBVEgiIC4KfQoKZGF0YXJlc3RvcmUoKSB7"\
"CiAgICBfY2hlY2tfcmVxdWlyZWRfZW52X3ZhcnMgIkJBQ0tVUF9GSUxFIiAiVEVNUF9ESVIiCiAg"\
"ICBCQUNLVVBfVEVNUF9QQVRIPSIkVEVNUF9ESVIvcmVzdG9yZSIKCiAgICBlY2hvICJfYXV0b2Nv"\
"bW1hbmRwYXJzZSBbcmVzdG9yZV0gWyRCQUNLVVBfVEVNUF9QQVRIXSBbJEBdIgoKICAgIG1rZGly"\
"IC1wICIkQkFDS1VQX1RFTVBfUEFUSCIKICAgIHRhciB6eHZmICIkQkFDS1VQX0ZJTEUiIC1DICIk"\
"QkFDS1VQX1RFTVBfUEFUSCIgLS1zdHJpcC1jb21wb25lbnRzPTEKCiAgICBfYXV0b2NvbW1hbmRw"\
"YXJzZSByZXN0b3JlICIkQkFDS1VQX1RFTVBfUEFUSCIgIiRAIgp9Cg==";
// 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, 6443138635497166205ULL, 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";
static const char filedata_base64[] = "IyEvYmluL2Jhc2gKCiMgVGhpcyBzY3JpcHQgY2hlY2tzIEFMTCBzZXJ2aWNlcyBvbiB0aGUgc2Vy"\
"dmVyIGFuZCByZXR1cm5zIGEgc3RhdHVzIGZvciBlYWNoLgoKIyBSZXR1cm4gZm9ybWF0IGlzIHNp"\
"bXBsZSBFTlYgd2l0aCB0aGUgZm9sbG93aW5nIGZvcm1hdDoKIyBTRVJWSUNFX05BTUVfSEVBTFRI"\
"PWhlYWx0aHl8dW5oZWFsdGh5fHVua25vd24KIyBTRVJWSUNFX05BTUVfUE9SVFM9cG9ydDEscG9y"\
"dDIscG9ydDMKCiMgR2V0IGFsbCBzZXJ2aWNlcyBvbiB0aGUgc2VydmVyClNDUklQVF9ESVI9IiQo"\
"ZGlybmFtZSAiJDAiKSIKCiAgICAjIC8vLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\
"LS0tLS0tCiAgICAjIC8vIHJlbW90ZSBwYXRocwogICAgIyAvLyBEUk9QU0hFTExfRElSCiAgICAj"\
"IC8vICAgfC0tIGJhY2t1cHMKICAgICMgLy8gICB8LS0gdGVtcF9maWxlcwogICAgIyAvLyAgIHwt"\
"LSBhZ2VudAogICAgIyAvLyAgIHwgICB8LS0gYmI2NAogICAgIyAvLyAgIHwgICB8LS0gKG90aGVy"\
"IGFnZW50IGZpbGVzLCBpbmNsdWRpbmcgX2FsbHNlcnZpY2Vzc3RhdHVzLnNoKQogICAgIyAvLyAg"\
"IHwtLSBzZXJ2aWNlcwogICAgIyAvLyAgICAgICB8LS0gc2VydmljZSBuYW1lCiAgICAjIC8vICAg"\
"ICAgICAgICB8LS0gY29uZmlnCiAgICAjIC8vICAgICAgICAgICAgICAgfC0tIHNlcnZpY2UuZW52"\
"IChhY3R1YWwgc2VydmljZSBjb25maWcpCiAgICAjIC8vICAgICAgICAgICAgICAgfC0tIC50ZW1w"\
"bGF0ZV9pbmZvLmVudgogICAgIyAvLyAgICAgICAgICAgfC0tIHRlbXBsYXRlCiAgICAjIC8vICAg"\
"ICAgICAgICAgICAgfC0tIF9kZWZhdWx0LmVudgogICAgIyAvLyAgICAgICAgICAgICAgIHwtLSAo"\
"c2NyaXB0IGZpbGVzKQogICAgIyAvLyAgICAgICAgICAgICAgIHwtLSBjb25maWcKICAgICMgLy8g"\
"ICAgICAgICAgICAgICAgICAgfC0tIHNlcnZpY2UuZW52IChkZWZhdWx0IHNlcnZpY2UgY29uZmln"\
"KQogICAgIyAvLyAgICAgICAgICAgICAgICAgICB8LS0gLnRlbXBsYXRlX2luZm8uZW52CiAgICAj"\
"IC8vICAgICAgICAgICAgICAgICAgIHwtLSAob3RoZXIgY29uZmlnIGZpbGVzIGZvciBzcGVjaWZp"\
"YyBzZXJ2ZXImc2VydmljZSkKCiMgR2V0IGFsbCBzZXJ2aWNlcyBvbiB0aGUgc2VydmVyClNFUlZJ"\
"Q0VTX1BBVEg9JChyZWFscGF0aCAiJHtTQ1JJUFRfRElSfS8uLi9zZXJ2aWNlcy8iKQoKQ1VSUkVO"\
"VF9PVVRQVVQ9IiIKQ1VSUkVOVF9FWElUX0NPREU9MAoKbG9hZF9kb3RlbnYoKXsKICAgIGxvY2Fs"\
"IGZpbGVfcGF0aD0kMQogICAgaWYgWyAtZiAiJHtmaWxlX3BhdGh9IiBdOyB0aGVuCiAgICAgICAg"\
"c291cmNlICIke2ZpbGVfcGF0aH0iCiAgICBmaQp9CgpfY2hlY2tfcmVxdWlyZWRfZW52X3ZhcnNf"\
"YWxsc2VydmljZXNzdGF0dXMoKSB7CiAgICBsb2NhbCByZXF1aXJlZF92YXJzPSgiJEAiKQogICAg"\
"Zm9yIHZhciBpbiAiJHtyZXF1aXJlZF92YXJzW0BdfSI7IGRvCiAgICAgICAgaWYgWyAteiAiJHsh"\
"dmFyfSIgXTsgdGhlbgogICAgICAgICAgICBfZGllICJSZXF1aXJlZCBlbnZpcm9ubWVudCB2YXJp"\
"YWJsZSAkdmFyIGlzIG5vdCBzZXQiCiAgICAgICAgZmkKICAgIGRvbmUKfQoKZnVuY3Rpb24gcnVu"\
"X2NvbW1hbmQoKSB7CiAgICBsb2NhbCBzZXJ2aWNlX3BhdGg9JDEKICAgIGxvY2FsIGNvbW1hbmQ9"\
"JDIKICAgIGxvY2FsIGNhcHR1cmVfb3V0cHV0PSR7MzotZmFsc2V9ICAjIGRlZmF1bHQgdG8gZmFs"\
"c2UgaWYgbm90IHNwZWNpZmllZAoKICAgICMgY2hlY2sgaWYgdGhlIGNvbW1hbmQgaXMgYSBmaWxl"\
"CiAgICBpZiBbICEgLWYgIiR7c2VydmljZV9wYXRofS90ZW1wbGF0ZS8ke2NvbW1hbmR9LnNoIiBd"\
"OyB0aGVuCiAgICAgICAgcmV0dXJuOwogICAgZmkKCiAgICAjIHJ1biB0aGUgY29tbWFuZCBpbiBh"\
"IHN1YnNoZWxsIHRvIHByZXZlbnQgZW52aXJvbm1lbnQgY2hhbmdlcwogICAgQ1VSUkVOVF9PVVRQ"\
"VVQ9JCgKICAgICAgICBzZXQgLWEKCiAgICAgICAgbG9hZF9kb3RlbnYgIiR7c2VydmljZV9wYXRo"\
"fS90ZW1wbGF0ZS9fZGVmYXVsdC5lbnYiCiAgICAgICAgbG9hZF9kb3RlbnYgIiR7c2VydmljZV9w"\
"YXRofS9jb25maWcvc2VydmljZS5lbnYiCiAgICAgICAgbG9hZF9kb3RlbnYgIiR7c2VydmljZV9w"\
"YXRofS9jb25maWcvLnRlbXBsYXRlX2luZm8uZW52IgoKICAgICAgICAjIHVwZGF0ZSB0aGUgbWFp"\
"biB2YXJpYWJsZXMuCiAgICAgICAgQ09ORklHX1BBVEg9IiR7c2VydmljZV9wYXRofS9jb25maWci"\
"CiAgICAgICAgU0VSVklDRT0iJHtTRVJWSUNFX05BTUV9IgogICAgICAgIERPQ0tFUl9DTElfSElO"\
"VFM9ZmFsc2UKCiAgICAgICAgc2V0ICthCgogICAgICAgIF9jaGVja19yZXF1aXJlZF9lbnZfdmFy"\
"c19hbGxzZXJ2aWNlc3N0YXR1cyAiQ09ORklHX1BBVEgiICJTRVJWRVIiICJTRVJWSUNFIiAiQUdF"\
"TlRfUEFUSCIgIkhPU1RfTkFNRSIgIlRFTVBMQVRFIgoKICAgICAgICBpZiBbICIkY2FwdHVyZV9v"\
"dXRwdXQiID0gInRydWUiIF07IHRoZW4KICAgICAgICAgICAgIyBDYXB0dXJlIGFuZCByZXR1cm4g"\
"b3V0cHV0CiAgICAgICAgICAgIGJhc2ggIiR7c2VydmljZV9wYXRofS90ZW1wbGF0ZS8ke2NvbW1h"\
"bmR9LnNoIiAyPiYxCiAgICAgICAgZWxzZQogICAgICAgICAgICAjIFJ1biBzaWxlbnRseSBhbmQg"\
"cmV0dXJuIGV4aXQgY29kZQogICAgICAgICAgICBiYXNoICIke3NlcnZpY2VfcGF0aH0vdGVtcGxh"\
"dGUvJHtjb21tYW5kfS5zaCIgPiAvZGV2L251bGwgMj4mMQogICAgICAgIGZpCiAgICApCiAgICBD"\
"VVJSRU5UX0VYSVRfQ09ERT0kPwp9CgpmdW5jdGlvbiBjb21tYW5kX2V4aXN0cygpIHsKICAgIGxv"\
"Y2FsIHNlcnZpY2VfcGF0aD0kMQogICAgbG9jYWwgY29tbWFuZD0kMgogICAgaWYgWyAhIC1mICIk"\
"e3NlcnZpY2VfcGF0aH0vdGVtcGxhdGUvJHtjb21tYW5kfS5zaCIgXTsgdGhlbgogICAgICAgIHJl"\
"dHVybiAxCiAgICBmaQogICAgcmV0dXJuIDAKfQoKCmlmIFsgISAtZCAiJHtTRVJWSUNFU19QQVRI"\
"fSIgXTsgdGhlbgogICAgZWNobyAiU2VydmljZXMgcGF0aCBkb2VzIG5vdCBleGlzdDogJHtTRVJW"\
"SUNFU19QQVRIfSIKICAgIGV4aXQgMApmaQoKIyBHZXQgYWxsIHNlcnZpY2UgbmFtZXMKU0VSVklD"\
"RV9OQU1FUz0kKGxzICIke1NFUlZJQ0VTX1BBVEh9IikKCiMgSXRlcmF0ZSBvdmVyIGFsbCBzZXJ2"\
"aWNlIG5hbWVzCmZvciBTRVJWSUNFX05BTUUgaW4gJHtTRVJWSUNFX05BTUVTfTsgZG8KCiAgICBT"\
"RVJWSUNFX1BBVEg9JChyZWFscGF0aCAiJHtTRVJWSUNFU19QQVRIfS8ke1NFUlZJQ0VfTkFNRX0i"\
"KQoKICAgICMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgIyBHZXQgdGhlIHNl"\
"cnZpY2UgaGVhbHRoCiAgICBpZiAhIGNvbW1hbmRfZXhpc3RzICIke1NFUlZJQ0VfUEFUSH0iICJz"\
"dGF0dXMiOyB0aGVuCiAgICAgICAgU0VSVklDRV9IRUFMVEg9InVua25vd24iCiAgICBlbHNlCiAg"\
"ICAgICAgcnVuX2NvbW1hbmQgIiR7U0VSVklDRV9QQVRIfSIgInN0YXR1cyIgImZhbHNlIgogICAg"\
"ICAgIGlmIFsgIiR7Q1VSUkVOVF9FWElUX0NPREV9IiAtZXEgMCBdOyB0aGVuCiAgICAgICAgICAg"\
"IFNFUlZJQ0VfSEVBTFRIPSJoZWFsdGh5IgogICAgICAgIGVsc2UKICAgICAgICAgICAgU0VSVklD"\
"RV9IRUFMVEg9InVuaGVhbHRoeSIKICAgICAgICBmaQogICAgZmkKCiAgICAjLS0tLS0tLS0tLS0t"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgICMgR2V0IHRoZSBzZXJ2aWNlIHBvcnRzCiAgICBpZiAh"\
"IGNvbW1hbmRfZXhpc3RzICIke1NFUlZJQ0VfUEFUSH0iICJwb3J0cyI7IHRoZW4KICAgICAgICBT"\
"RVJWSUNFX1BPUlRTPSIiCiAgICBlbHNlCiAgICAgICAgcnVuX2NvbW1hbmQgIiR7U0VSVklDRV9Q"\
"QVRIfSIgInBvcnRzIiAidHJ1ZSIKICAgICAgICBTRVJWSUNFX1BPUlRTPSIke0NVUlJFTlRfT1VU"\
"UFVUfSIKICAgIGZpCgogICAgIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICAj"\
"IHJldHVybiB0aGUgaGVhbHRoIGFuZCBwb3J0cwogICAgZWNobyAiJHtTRVJWSUNFX05BTUV9X0hF"\
"QUxUSD0ke1NFUlZJQ0VfSEVBTFRIfSIKICAgIGVjaG8gIiR7U0VSVklDRV9OQU1FfV9QT1JUUz0k"\
"e1NFUlZJQ0VfUE9SVFN9Igpkb25lCg==";
// 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, 4669115953916396805ULL, std::filesystem::perms(493), decoded_data, actual_size);
delete[] decoded_data;
any_written = any_written || file_written;
}
{
// File: common.sh
fs::path outpath = fs::path(destination_folder) / "common.sh";
static const char filedata_base64[] = "IyBDT01NT04gRlVOQ1RJT05TCiMgSkRFCiMgMjAyNS0wNS0wMwoKIyBUaGlzIGZpbGUgaXMgYXZh"\
"aWxhYmxlIFRPICoqKkFMTCoqKiB0ZW1wbGF0ZXMsIGFzICR7QUdFTlRfUEFUSH0vX2NvbW1vbi5z"\
"aAoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoj"\
"IHN1bW1hcnkgb2YgZnVuY3Rpb25zOgojICAgX2RpZSAibWVzc2FnZSIgICAgICAgICAgICAgICAg"\
"ICAgICAgICAgOiBQcmludHMgYW4gZXJyb3IgbWVzc2FnZSBpbiByZWQgYW5kIGV4aXRzIHdpdGgg"\
"c3RhdHVzIGNvZGUgMS4KIyAgIF9ncmV5X3N0YXJ0ICAgICAgICAgICAgICAgICAgICAgICAgICAg"\
"IDogU3dpdGNoZXMgdGVybWluYWwgb3V0cHV0IGNvbG9yIHRvIGdyZXkuCiMgICBfZ3JleV9lbmQg"\
"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IFJlc2V0cyB0ZXJtaW5hbCBvdXRwdXQgY29s"\
"b3IgZnJvbSBncmV5LgojICAgX2NyZWF0ZV9hbmRfc3RhcnRfY29udGFpbmVyICI8cnVuX2NtZD4i"\
"IDxjb250YWluZXJfbmFtZT4gOiBDcmVhdGVzL3N0YXJ0cyBhIGNvbnRhaW5lciwgdmVyaWZ5aW5n"\
"IGl0IHJ1bnMuCiMgICBfY3JlYXRlX2ZvbGRlciA8Zm9sZGVyX3BhdGg+ICAgICAgICAgICA6IENy"\
"ZWF0ZXMgYSBkaXJlY3RvcnkgaWYgaXQgZG9lc24ndCBleGlzdCAoY2htb2QgNzc3KS4KIyAgIF9j"\
"aGVja19kb2NrZXJfaW5zdGFsbGVkICAgICAgICAgICAgICAgOiBDaGVja3MgaWYgRG9ja2VyIGlz"\
"IGluc3RhbGxlZCwgcnVubmluZywgYW5kIHVzZXIgaGFzIHBlcm1pc3Npb24uIFJldHVybnMgMSBv"\
"biBmYWlsdXJlLgojICAgX2lzX2NvbnRhaW5lcl9leGlzdHMgPGNvbnRhaW5lcl9uYW1lPiA6IENo"\
"ZWNrcyBpZiBhIGNvbnRhaW5lciAoYW55IHN0YXRlKSBleGlzdHMuIFJldHVybnMgMSBpZiBub3Qg"\
"Zm91bmQuCiMgICBfaXNfY29udGFpbmVyX3J1bm5pbmcgPGNvbnRhaW5lcl9uYW1lPjogQ2hlY2tz"\
"IGlmIGEgY29udGFpbmVyIGlzIGN1cnJlbnRseSBydW5uaW5nLiBSZXR1cm5zIDEgaWYgbm90IHJ1"\
"bm5pbmcuCiMgICBfZ2V0X2NvbnRhaW5lcl9pZCA8Y29udGFpbmVyX25hbWU+ICAgIDogUHJpbnRz"\
"IHRoZSBJRCBvZiB0aGUgbmFtZWQgY29udGFpbmVyLgojICAgX2dldF9jb250YWluZXJfc3RhdHVz"\
"IDxjb250YWluZXJfbmFtZT46IFByaW50cyB0aGUgc3RhdHVzIHN0cmluZyBvZiB0aGUgbmFtZWQg"\
"Y29udGFpbmVyLgojICAgX3N0YXJ0X2NvbnRhaW5lciA8Y29udGFpbmVyX25hbWU+ICAgICA6IFN0"\
"YXJ0cyBhbiBleGlzdGluZywgc3RvcHBlZCBjb250YWluZXIuCiMgICBfc3RvcF9jb250YWluZXIg"\
"PGNvbnRhaW5lcl9uYW1lPiAgICAgIDogU3RvcHMgYSBydW5uaW5nIGNvbnRhaW5lci4KIyAgIF9y"\
"ZW1vdmVfY29udGFpbmVyIDxjb250YWluZXJfbmFtZT4gICAgOiBTdG9wcyAoaWYgbmVlZGVkKSBh"\
"bmQgcmVtb3ZlcyBhIGNvbnRhaW5lci4KIyAgIF9nZXRfY29udGFpbmVyX2xvZ3MgPGNvbnRhaW5l"\
"cl9uYW1lPiAgOiBQcmludHMgdGhlIGxvZ3MgZm9yIGEgY29udGFpbmVyLgojICAgX2NoZWNrX3Jl"\
"cXVpcmVkX2Vudl92YXJzICJWQVIxIiAuLi4gICAgOiBDaGVja3MgaWYgbGlzdGVkIGVudmlyb25t"\
"ZW50IHZhcmlhYmxlcyBhcmUgc2V0OyBjYWxscyBfZGllKCkgaWYgYW55IGFyZSBtaXNzaW5nLgoj"\
"ICAgX3Jvb3RfcmVtb3ZlX3RyZWUgPHBhdGg+ICAgICAgICAgICAgICA6IFJlbW92ZXMgYSBwYXRo"\
"IHVzaW5nIGEgcm9vdCBEb2NrZXIgY29udGFpbmVyIChmb3IgcGVybWlzc2lvbnMpLgoKIyAtLS0t"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFByaW50cyBh"\
"biBlcnJvciBtZXNzYWdlIGluIHJlZCBhbmQgZXhpdHMgd2l0aCBzdGF0dXMgY29kZSAxLgpfZGll"\
"KCkgewogICAgZWNobyAtZSAiRXJyb3I6ICQxIgogICAgZXhpdCAxCn0KCiMgQ3JlYXRlcy9zdGFy"\
"dHMgYSBjb250YWluZXIsIHZlcmlmeWluZyBpdCBydW5zLgpfY3JlYXRlX2FuZF9zdGFydF9jb250"\
"YWluZXIoKSB7CiAgICBpZiBbIC16ICIkMSIgXSB8fCBbIC16ICIkMiIgXTsgdGhlbgogICAgICAg"\
"IF9kaWUgIlRlbXBsYXRlIGVycm9yOiBjcmVhdGVfYW5kX3N0YXJ0X2NvbnRhaW5lciA8cnVuX2Nt"\
"ZD4gPGNvbnRhaW5lcl9uYW1lPiIKICAgIGZpCgogICAgbG9jYWwgcnVuX2NtZD0iJDEiCiAgICBs"\
"b2NhbCBjb250YWluZXJfbmFtZT0iJDIiCgogICAgaWYgX2lzX2NvbnRhaW5lcl9leGlzdHMgJGNv"\
"bnRhaW5lcl9uYW1lOyB0aGVuCiAgICAgICAgX2lzX2NvbnRhaW5lcl9ydW5uaW5nICRjb250YWlu"\
"ZXJfbmFtZSAmJiByZXR1cm4gMAogICAgICAgIF9zdGFydF9jb250YWluZXIgJGNvbnRhaW5lcl9u"\
"YW1lCiAgICBlbHNlCiAgICAgICAgJHJ1bl9jbWQKICAgIGZpCgogICAgaWYgISBfaXNfY29udGFp"\
"bmVyX3J1bm5pbmcgJGNvbnRhaW5lcl9uYW1lOyB0aGVuCiAgICAgICAgX2RpZSAiQ29udGFpbmVy"\
"ICR7Y29udGFpbmVyX25hbWV9IGZhaWxlZCB0byBzdGFydCIKICAgIGZpCgogICAgSUQ9JChfZ2V0"\
"X2NvbnRhaW5lcl9pZCAkY29udGFpbmVyX25hbWUpCiAgICBlY2hvICJDb250YWluZXIgJHtjb250"\
"YWluZXJfbmFtZX0gaXMgcnVubmluZyB3aXRoIElEICR7SUR9Igp9CgojIENyZWF0ZXMgYSBkaXJl"\
"Y3RvcnkgaWYgaXQgZG9lc24ndCBleGlzdCAoY2htb2QgNzc3KS4KX2NyZWF0ZV9mb2xkZXIoKSB7"\
"CiAgICBsb2NhbCBmb2xkZXI9IiQxIgogICAgaWYgWyAtZCAiJGZvbGRlciIgXTsgdGhlbgogICAg"\
"ICAgIHJldHVybiAwCiAgICBmaQogICAgaWYgISBta2RpciAtcCAiJGZvbGRlciI7IHRoZW4KICAg"\
"ICAgICBfZGllICJGYWlsZWQgdG8gY3JlYXRlIGZvbGRlcjogJGZvbGRlciIKICAgIGZpCiAgICBj"\
"aG1vZCA3NzcgIiRmb2xkZXIiCiAgICBlY2hvICJGb2xkZXIgY3JlYXRlZDogJGZvbGRlciIKfQoK"\
"IyBDaGVja3MgaWYgRG9ja2VyIGlzIGluc3RhbGxlZCwgcnVubmluZywgYW5kIHVzZXIgaGFzIHBl"\
"cm1pc3Npb24uIFJldHVybnMgMSBvbiBmYWlsdXJlLgpfY2hlY2tfZG9ja2VyX2luc3RhbGxlZCgp"\
"IHsKICAgIGlmICEgY29tbWFuZCAtdiBkb2NrZXIgJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAg"\
"ZWNobyAiRG9ja2VyIGlzIG5vdCBpbnN0YWxsZWQiCiAgICAgICAgcmV0dXJuIDEKICAgIGZpCgog"\
"ICAgIyBjaGVjayBpZiBkb2NrZXIgZGFlbW9uIGlzIHJ1bm5pbmcKICAgIGlmICEgZG9ja2VyIGlu"\
"Zm8gJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAgZWNobyAiRG9ja2VyIGRhZW1vbiBpcyBub3Qg"\
"cnVubmluZyIKICAgICAgICByZXR1cm4gMQogICAgZmkKCiAgICAjIGNoZWNrIGlmIHVzZXIgaGFz"\
"IHBlcm1pc3Npb24gdG8gcnVuIGRvY2tlcgogICAgaWYgISBkb2NrZXIgcnVuIC0tcm0gaGVsbG8t"\
"d29ybGQgJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAgZWNobyAiVXNlciBkb2VzIG5vdCBoYXZl"\
"IHBlcm1pc3Npb24gdG8gcnVuIGRvY2tlciIKICAgICAgICByZXR1cm4gMQogICAgZmkKCiAgICBy"\
"ZXR1cm4gMAp9CgojIENoZWNrcyBpZiBhIGNvbnRhaW5lciAoYW55IHN0YXRlKSBleGlzdHMuIFJl"\
"dHVybnMgMSBpZiBub3QgZm91bmQuCl9pc19jb250YWluZXJfZXhpc3RzKCkgewogICAgaWYgISBk"\
"b2NrZXIgcHMgLWEgLS1mb3JtYXQgInt7Lk5hbWVzfX0iIHwgZ3JlcCAtcSAiXiQxJCI7IHRoZW4K"\
"ICAgICAgICByZXR1cm4gMQogICAgZmkKICAgIHJldHVybiAwCn0KCiMgQ2hlY2tzIGlmIGEgY29u"\
"dGFpbmVyIGlzIGN1cnJlbnRseSBydW5uaW5nLiBSZXR1cm5zIDEgaWYgbm90IHJ1bm5pbmcuCl9p"\
"c19jb250YWluZXJfcnVubmluZygpIHsKICAgIGlmICEgZG9ja2VyIHBzIC0tZm9ybWF0ICJ7ey5O"\
"YW1lc319IiB8IGdyZXAgLXEgIl4kMSQiOyB0aGVuCiAgICAgICAgcmV0dXJuIDEKICAgIGZpCiAg"\
"ICByZXR1cm4gMAp9CgojIFByaW50cyB0aGUgSUQgb2YgdGhlIG5hbWVkIGNvbnRhaW5lci4KX2dl"\
"dF9jb250YWluZXJfaWQoKSB7CiAgICBkb2NrZXIgcHMgLS1mb3JtYXQgInt7LklEfX0iIC0tZmls"\
"dGVyICJuYW1lPSQxIgp9CgojIFByaW50cyB0aGUgc3RhdHVzIHN0cmluZyBvZiB0aGUgbmFtZWQg"\
"Y29udGFpbmVyLgpfZ2V0X2NvbnRhaW5lcl9zdGF0dXMoKSB7CiAgICBkb2NrZXIgcHMgLS1mb3Jt"\
"YXQgInt7LlN0YXR1c319IiAtLWZpbHRlciAibmFtZT0kMSIKfQoKIyBTdGFydHMgYW4gZXhpc3Rp"\
"bmcsIHN0b3BwZWQgY29udGFpbmVyLgpfc3RhcnRfY29udGFpbmVyKCkgewogICAgX2lzX2NvbnRh"\
"aW5lcl9leGlzdHMgJDEgfHwgcmV0dXJuIDEKICAgIF9pc19jb250YWluZXJfcnVubmluZyAkMSAm"\
"JiByZXR1cm4gMAogICAgZG9ja2VyIHN0YXJ0ICQxCn0KCiMgU3RvcHMgYSBydW5uaW5nIGNvbnRh"\
"aW5lci4KX3N0b3BfY29udGFpbmVyKCkgewogICAgX2lzX2NvbnRhaW5lcl9ydW5uaW5nICQxIHx8"\
"IHJldHVybiAwOwogICAgZG9ja2VyIHN0b3AgJDEKfSAgIAoKIyBTdG9wcyAoaWYgbmVlZGVkKSBh"\
"bmQgcmVtb3ZlcyBhIGNvbnRhaW5lci4KX3JlbW92ZV9jb250YWluZXIoKSB7CiAgICBfc3RvcF9j"\
"b250YWluZXIgJDEKICAgIF9pc19jb250YWluZXJfZXhpc3RzICQxIHx8IHJldHVybiAwOwogICAg"\
"ZG9ja2VyIHJtICQxCn0KCiMgUHJpbnRzIHRoZSBsb2dzIGZvciBhIGNvbnRhaW5lci4KX2dldF9j"\
"b250YWluZXJfbG9ncygpIHsKICAgIGlmICEgX2lzX2NvbnRhaW5lcl9leGlzdHMgJDE7IHRoZW4K"\
"ICAgICAgICBlY2hvICJDb250YWluZXIgJDEgZG9lcyBub3QgZXhpc3QiCiAgICAgICAgcmV0dXJu"\
"IDEKICAgIGZpCgogICAgZG9ja2VyIGxvZ3MgJDEKfQoKIyBDaGVja3MgaWYgbGlzdGVkIGVudmly"\
"b25tZW50IHZhcmlhYmxlcyBhcmUgc2V0OyBjYWxscyBfZGllKCkgaWYgYW55IGFyZSBtaXNzaW5n"\
"LgpfY2hlY2tfcmVxdWlyZWRfZW52X3ZhcnMoKSB7CiAgICBsb2NhbCByZXF1aXJlZF92YXJzPSgi"\
"JEAiKQogICAgZm9yIHZhciBpbiAiJHtyZXF1aXJlZF92YXJzW0BdfSI7IGRvCiAgICAgICAgaWYg"\
"WyAteiAiJHshdmFyfSIgXTsgdGhlbgogICAgICAgICAgICBfZGllICJSZXF1aXJlZCBlbnZpcm9u"\
"bWVudCB2YXJpYWJsZSAkdmFyIGlzIG5vdCBzZXQiCiAgICAgICAgZmkKICAgIGRvbmUKfQoKIyBS"\
"ZW1vdmVzIGEgcGF0aCB1c2luZyBhIHJvb3QgRG9ja2VyIGNvbnRhaW5lciAoZm9yIHBlcm1pc3Np"\
"b25zKS4KX3Jvb3RfcmVtb3ZlX3RyZWUoKSB7CiAgICBsb2NhbCB0b19yZW1vdmU9IiQxIgogICAg"\
"cGFyZW50PSQoZGlybmFtZSAiJHRvX3JlbW92ZSIpCiAgICBhYnNfcGFyZW50PSQocmVhbHBhdGgg"\
"IiRwYXJlbnQiKQogICAgY2hpbGQ9JChiYXNlbmFtZSAiJHRvX3JlbW92ZSIpCiAgICBkb2NrZXIg"\
"cnVuIC0tcm0gLXYgIiRhYnNfcGFyZW50IjovZGF0YSBhbHBpbmUgcm0gLXJmICIvZGF0YS8kY2hp"\
"bGQiCn0KCgojIExvYWQgYXV0b2NvbW1hbmRzCnNvdXJjZSAiJHtBR0VOVF9QQVRIfS9kYXRhY29t"\
"bWFuZHMuc2gi";
// 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, 6967493376886731479ULL, std::filesystem::perms(493), decoded_data, actual_size);
delete[] decoded_data;
any_written = any_written || file_written;
}
return any_written;
}
}

View File

@ -1,15 +0,0 @@
#pragma once
/*
THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
DO NOT EDIT THIS FILE.
*/
#include <string>
namespace recreate_agent {
bool recreate_tree(std::string destination_folder);
}

View File

@ -1,11 +0,0 @@
// version.hpp (dummy for linter/IntelliSense)
#pragma once
#include <string>
namespace dropshell {
extern const std::string VERSION;
extern const std::string RELEASE_DATE;
extern const std::string AUTHOR;
extern const std::string LICENSE;
}

View File

@ -0,0 +1,172 @@
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/output.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "command_registry.hpp"
#include "config.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "servers.hpp"
#include "templates.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
namespace dropshell
{
int backupdata_handler(const CommandContext &ctx);
static std::vector<std::string> backupdata_name_list = {"backupdata", "bd", "backup", "bup"};
// Static registration
struct BackupDataCommandRegister
{
BackupDataCommandRegister()
{
CommandRegistry::instance().register_command({backupdata_name_list,
backupdata_handler,
shared_commands::std_autocomplete_allowall,
false, // hidden
true, // requires_config
true, // requires_install
2, // min_args (after command)
2, // max_args (after command)
"backupdata SERVER SERVICE",
"Backup data for a service on a server.",
// heredoc
R"(
backupdata SERVER SERVICE Backup data for a service on a server.
backupdata SERVER all Backup data for all services on a server.
Note: This command will not create any data or configuration.
It will simply backup the data on the remote server, saving it to a local file.
Restore the data with restore.
)"});
}
} backupdata_command_register;
namespace shared_commands
{
bool backupdata_service(const ServerConfig &server_env, const std::string &service)
{
ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name());
std::string server = server_env.get_server_name();
LocalServiceInfo sinfo = get_service_info(server, service);
if (!SIvalid(sinfo))
{
error << "Service " << service << " is not valid" << std::endl;
return false;
}
const std::string command = "backup";
if (!gTemplateManager().template_command_exists(sinfo.template_name, command))
{
info << service << " has no data to backup" << std::endl;
debug << "(no backup script for " << sinfo.template_name << ")" << std::endl;
return true; // nothing to back up.
}
std::string user = server_env.get_user_for_service(service);
// Check if basic installed stuff is in place.
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(server, user).service_config(service);
if (!server_env.check_remote_items_exist({remotepath(server, user).service(service),
remote_command_script_file,
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;
return false;
}
// Create backups directory on server if it doesn't exist
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(user), sCommand("", mkdir_cmd, {}), cMode::Defaults))
{
error << "Failed to create backups directory on server" << std::endl;
return false;
}
// Create backups directory locally if it doesn't exist
std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty())
{
error << "Error: Local backups directory not found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false;
}
if (!std::filesystem::exists(local_backups_dir))
std::filesystem::create_directories(local_backups_dir);
// Get current datetime for backup filename
shared_commands::cBackupFileName backup_filename_construction(server, service, sinfo.template_name);
if (!backup_filename_construction.is_valid())
{
error << "Invalid backup filename" << std::endl;
return false;
}
// Construct backup filename
std::string backup_filename = backup_filename_construction.get_filename();
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_filename;
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string();
// assert that the backup filename is valid - -_- appears exactly 3 times in local_backup_file_path.
ASSERT(3 == count_substring(magic_string(), local_backup_file_path), "Invalid backup filename");
{ // Run backup script
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;
return false;
}
// Copy backup file from server to local
if (!shared_commands::scp_file_from_remote(server_env, remote_backup_file_path, local_backup_file_path, false, sinfo.user))
{
error << "Failed to copy backup file from server" << std::endl;
return false;
}
} // dtor of remote_temp_folder will clean up the temp folder on the server
info << "Backup created successfully. Restore with:" << std::endl;
info << " dropshell restore " << server << " " << service << " " << backup_filename << std::endl;
return true;
}
} // namespace shared_commands
int backupdata_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 2, "Invalid number of arguments");
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
// backup all services on the server
maketitle("Backing up data for all services on " + server);
bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const LocalServiceInfo &si : services)
okay &= shared_commands::backupdata_service(server, si.service_name);
return okay ? 0 : 1;
}
return shared_commands::backupdata_service(server, service) ? 0 : 1;
}
} // namespace dropshell

View File

@ -0,0 +1,63 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "version.hpp"
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/assert.hpp"
namespace dropshell {
void create_server_autocomplete(const CommandContext& ctx);
int create_server_handler(const CommandContext& ctx);
static std::vector<std::string> create_server_name_list={"create-server"};
// Static registration
struct CreateServerCommandRegister {
CreateServerCommandRegister() {
CommandRegistry::instance().register_command({
create_server_name_list,
create_server_handler,
create_server_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (after command)
1, // max_args (after command)
"create-server [SERVER]",
"Create a new server entry on this host.",
// heredoc
R"(
Create a new server entry on this host.
Note you will need to use ds install SERVER to prepare the service for use.
create-server SERVER
)"
});
}
} create_server_command_register;
void create_server_autocomplete(const CommandContext& ctx) {
return; // can't autocomplete as it's a new server!
}
int create_server_handler(const CommandContext& ctx) {
// create a new server entry on this host
if (ctx.args.size() == 0) {
error << "No server name provided" << std::endl;
return 1;
}
bool ok = create_server(ctx.args[0]);
return ok ? 0 : 1;
}
} // namespace dropshell

View File

@ -7,11 +7,12 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "services.hpp" #include "services.hpp"
#include <fstream>
namespace dropshell namespace dropshell
{ {
int create_service_handler(const CommandContext &ctx); int create_service_handler(const CommandContext &ctx);
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent);
void create_service_autocomplete(const CommandContext &ctx); void create_service_autocomplete(const CommandContext &ctx);
static std::vector<std::string> create_service_name_list = {"create-service"}; static std::vector<std::string> create_service_name_list = {"create-service"};
@ -45,7 +46,7 @@ namespace dropshell
std::string service = safearg(ctx.args, 1); std::string service = safearg(ctx.args, 1);
std::string template_name = safearg(ctx.args, 2); std::string template_name = safearg(ctx.args, 2);
return create_service(server, template_name, service, false) ? 0 : 1; return shared_commands::create_service(server, template_name, service) ? 0 : 1;
} }
void create_service_autocomplete(const CommandContext &ctx) void create_service_autocomplete(const CommandContext &ctx)
@ -58,58 +59,89 @@ namespace dropshell
{ {
std::set<std::string> templates = gTemplateManager().get_template_list(); std::set<std::string> templates = gTemplateManager().get_template_list();
for (const auto &template_name : templates) for (const auto &template_name : templates)
std::cout << template_name << std::endl; rawout << template_name << std::endl;
} }
} }
} }
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent) namespace shared_commands
{
bool print_readme(const template_info &tinfo, std::string server, std::string service)
{
std::vector<std::string> variants_to_try = {"README.txt", "readme.txt", "ReadMe.txt", "README", "readme", "README.md", "readme.md"};
std::filesystem::path readme_path = tinfo.local_template_path();
for (const auto &variant : variants_to_try)
{
if (std::filesystem::exists(readme_path / variant))
{
readme_path = readme_path / variant;
break;
}
}
if (!std::filesystem::exists(readme_path))
return false;
std::map<std::string, std::string> all_env_vars;
get_all_service_env_vars(server, service, all_env_vars);
all_env_vars["LOCAL_CONFIG_PATH"] = localpath::service(server, service);
all_env_vars["LOCAL_TEMPLATE_PATH"] = tinfo.local_template_path().string();
info << std::endl;
std::ifstream readme_file(readme_path);
std::string line;
while (std::getline(readme_file, line))
{
rawout << substitute_provided_key_value_pairs(line, all_env_vars) << std::endl;
}
return true;
}
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override/*=""*/)
{ {
if (server_name.empty() || template_name.empty() || service_name.empty()) if (server_name.empty() || template_name.empty() || service_name.empty())
return false; return false;
ServerConfig server_info(server_name);
if (!server_info.is_valid())
{
error << "Server " << server_name << " is not valid" << std::endl;
return false;
}
std::string service_dir = localpath::service(server_name, service_name); std::string service_dir = localpath::service(server_name, service_name);
if (service_dir.empty()) if (service_dir.empty())
{ {
if (!silent) error << "Couldn't locate server " << server_name << " in any config directory" << std::endl;
{ info << "Please check the server name is correct and try again" << std::endl;
std::cerr << "Error: Couldn't locate server " << server_name << " in any config directory" << std::endl; info << "You can list all servers with 'dropshell servers'" << std::endl;
std::cerr << "Please check the server name is correct and try again" << std::endl; info << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
std::cerr << "You can list all servers with 'dropshell servers'" << std::endl;
std::cerr << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
}
return false; return false;
} }
if (std::filesystem::exists(service_dir)) if (std::filesystem::exists(service_dir))
{ {
if (!silent) error << "Service already exists: " << service_name << std::endl;
{ debug << "Current service path: " << service_dir << std::endl;
std::cerr << "Error: Service already exists: " << service_name << std::endl;
std::cerr << "Current service path: " << service_dir << std::endl;
}
return false; return false;
} }
template_info tinfo = gTemplateManager().get_template_info(template_name); template_info tinfo = gTemplateManager().get_template_info(template_name);
if (!tinfo.is_set()) if (!tinfo.is_set())
{ {
if (!silent) error << "Template '" << template_name << "' not found" << std::endl;
{ info << "Please check the template name is correct and try again" << std::endl;
std::cerr << "Error: Template '" << template_name << "' not found" << std::endl; info << "You can list all templates with 'dropshell templates'" << std::endl;
std::cerr << "Please check the template name is correct and try again" << std::endl; info << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
std::cerr << "You can list all templates with 'dropshell templates'" << std::endl;
std::cerr << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
}
return false; return false;
} }
// check template is all good. // check template is all good.
if (!gTemplateManager().test_template(tinfo.local_template_path())) if (!gTemplateManager().test_template(tinfo.local_template_path()))
{ {
if (!silent) error << "Template '" << template_name << "' is not valid" << std::endl;
std::cerr << "Error: Template '" << template_name << "' is not valid" << std::endl;
return false; return false;
} }
@ -119,15 +151,123 @@ namespace dropshell
// copy the template config files to the service directory // copy the template config files to the service directory
recursive_copy(tinfo.local_template_path() / "config", service_dir); recursive_copy(tinfo.local_template_path() / "config", service_dir);
if (!silent) // append TEMPLATE_HASH to the .template_info.env file
std::string template_info_env_file = localfile::template_info_env(server_name,service_name);
ASSERT(std::filesystem::exists(template_info_env_file), "Template info env file not found: " + template_info_env_file);
std::ofstream template_info_env_file_out(template_info_env_file, std::ios::app); // append to the file.
template_info_env_file_out << "TEMPLATE_HASH=" << tinfo.hash() << std::endl;
template_info_env_file_out.close();
// modify the SSH_USER to be nice.
// everything is created, so we can get the service info.
LocalServiceInfo service_info = get_service_info(server_name, service_name);
std::string sshuser = "root";
if (!user_override.empty())
sshuser = user_override;
else
if (!service_info.requires_host_root)
{ // find a non-root user.
auto users = server_info.get_users();
auto it = std::find_if(users.begin(), users.end(), [&sshuser](const UserConfig &user)
{ return user.user != "root"; });
if (it != users.end())
sshuser = it->user;
}
if (sshuser == "root" && !server_info.hasRootUser())
{ {
std::cout << "Service " << service_name << " created successfully" << std::endl; error << "Server " << server_name << " does not have a root user, but the service " << service_name << " requires it." << std::endl;
std::cout << std::endl; return false;
std::cout << "To complete the installation, please:" << std::endl; }
std::cout << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl; if (sshuser != "root" && service_info.requires_host_root)
std::cout << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl; {
error << "The service " << service_name << " requires a root user, but a non-root user was specified." << std::endl;
return false;
}
if (!server_info.hasUser(sshuser))
{
error << "User " << sshuser << "is not available on server " << server_name << std::endl;
return false;
}
info << "Setting SSH_USER to " << sshuser << " in the " << filenames::service_env << " file" << std::endl;
{ // edit the service.env file to set the SSH_USER.
std::string template_service_env_file = tinfo.local_template_path() / "config" / filenames::service_env;
ASSERT(std::filesystem::exists(template_service_env_file), "Template service env file not found: " + template_service_env_file);
std::ifstream template_service_env_file_in(template_service_env_file);
std::ofstream service_env_file_out(localfile::template_info_env(server_name, service_name));
std::string line;
while (std::getline(template_service_env_file_in, line))
{
if (line.find("SSH_USER") != std::string::npos)
line = "SSH_USER=" + sshuser;
service_env_file_out << line << std::endl;
}
template_service_env_file_in.close();
service_env_file_out.close();
}
// check docker.
if (service_info.requires_docker)
{
if (!server_info.hasDocker())
{
error << "Server " << server_name << " does not have docker, but the service " << service_name << " requires it." << std::endl;
return false;
}
if (service_info.requires_docker_root)
{
if (!server_info.hasRootDocker())
{
error << "Server " << server_name << " does not have a root docker, but the service " << service_name << " requires it." << std::endl;
return false;
}
}
}
info << "Service " << service_name << " created successfully" << std::endl;
if (!print_readme(tinfo, server_name, service_name))
{
info << std::endl;
info << "To complete the installation, please:" << std::endl;
info << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl;
info << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
} }
return true; return true;
} }
bool merge_updated_service_template(const std::string &server_name, const std::string &service_name)
{
LocalServiceInfo service_info = get_service_info(server_name, service_name);
ASSERT(SIvalid(service_info), "Service info is not valid for " + service_name + " on " + server_name);
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
ASSERT(tinfo.is_set(), "Failed to load template " + service_info.template_name);
// copy across .template_info.env file
std::string template_info_env_file = tinfo.local_template_path() / "config" / filenames::template_info_env;
std::string target_template_info_env_file = localfile::template_info_env(server_name, service_name);
ASSERT(std::filesystem::exists(template_info_env_file), "Template service env file not found: " + template_info_env_file);
std::filesystem::remove(target_template_info_env_file);
std::filesystem::copy(template_info_env_file, target_template_info_env_file);
#pragma message("TODO: merge the template info env file")
// update hash in template info env file
// append TEMPLATE_HASH to the .template_info.env file
ASSERT(std::filesystem::exists(target_template_info_env_file), "Template info env file not found: " + target_template_info_env_file);
std::ofstream template_info_env_file_out(target_template_info_env_file, std::ios::app); // append to the file.
template_info_env_file_out << "TEMPLATE_HASH=" << tinfo.hash() << std::endl;
template_info_env_file_out.close();
return true;
}
} // namespace shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -0,0 +1,63 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "version.hpp"
#include "utils/assert.hpp"
#include "templates.hpp"
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
namespace dropshell {
void create_template_autocomplete(const CommandContext& ctx);
int create_template_handler(const CommandContext& ctx);
static std::vector<std::string> create_template_name_list={"create-template"};
// Static registration
struct CreateTemplateCommandRegister {
CreateTemplateCommandRegister() {
CommandRegistry::instance().register_command({
create_template_name_list,
create_template_handler,
create_template_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (after command)
1, // max_args (after command)
"create-template TEMPLATE",
"Create a new template.",
// heredoc
R"(
Create a new template.
create-template TEMPLATE
)"
});
}
} create_template_command_register;
void create_template_autocomplete(const CommandContext& ctx) {
return; // can't autocomplete as it's a new server!
}
int create_template_handler(const CommandContext& ctx) {
// create a new server entry on this host
if (ctx.args.size() == 0) {
error << "No template name provided" << std::endl;
return 1;
}
bool ok = gTemplateManager().create_template(ctx.args[0]);
return ok ? 0 : 1;
}
} // namespace dropshell

View File

@ -0,0 +1,155 @@
#include "command_registry.hpp"
#include "shared_commands.hpp"
#include "config.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "utils/directories.hpp"
#include "servers.hpp"
#include "templates.hpp"
#include "utils/utils.hpp"
#include "utils/assert.hpp"
namespace dropshell
{
int nuke_handler(const CommandContext &ctx);
static std::vector<std::string> nuke_name_list = {"destroy","nuke"};
// Static registration
struct NukeCommandRegister
{
NukeCommandRegister()
{
CommandRegistry::instance().register_command({nuke_name_list,
nuke_handler,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
2, // min_args (after command)
2, // max_args (after command)
"destroy SERVER SERVICE|all",
"Destroy a service on a server. Erases everything, both local and remote!",
// heredoc
R"(
Nuke a service.
Examples:
destroy SERVER SERVICE destroy the given service on the given server.
destroy SERVER all destroy all services on the given server.
Note: This command is destructive and will destroy all data and all configuration,
both on the dropshell host and on the remote server.
Use with caution!
)"});
}
} nuke_command_register;
namespace shared_commands
{
bool nuke_service(const std::string &server, const std::string &service)
{
ServerConfig server_env(server);
// step 1 - nuke on remote server.
if (server_env.is_valid())
{
LocalServiceInfo service_info;
service_info = get_service_info(server, service);
if (!SIvalid(service_info))
error << "Invalid service: " << service << std::endl;
std::string user = server_env.get_user_for_service(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.
if (gTemplateManager().template_command_exists(service_info.template_name, "nuke"))
{
info << "Running nuke script for " << service << " on " << server << std::endl;
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
warning << "Failed to run nuke script: " << service << std::endl;
}
else
{
info << "No nuke script found for " << service << " on " << server << std::endl;
info << "Running uninstall script instead and will clean directories." << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Failed to uninstall service: " << service << std::endl;
}
// Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath(server, user).service(service), true, user))
{
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(server, user).service(service) << std::endl;
}
else
warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory.
std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{
warning << "Local service directory not found: " << local_service_path << std::endl;
}
else
{
auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0)
error << "Failed to remove local service directory" << std::endl;
}
info << "Nuked service " << service << " on server " << server << std::endl;
return true;
}
} // namespace shared_commands
int nuke_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 2, "Usage: nuke SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")");
ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration.");
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
int rval = 0;
// iterate through all service folders in the server directory.
std::string server_path = localpath::server(server);
if (server_path.empty())
{
error << "Server not found: " << server << std::endl;
return 1;
}
for (const auto &entry : std::filesystem::directory_iterator(server_path))
{
if (entry.is_directory() && entry.path().filename().string().find(".") != 0)
{
std::string service_name = entry.path().filename().string();
rval |= (shared_commands::nuke_service(server, service_name) ? 0 : 1);
}
}
return rval;
}
else
{
return (shared_commands::nuke_service(server, service) ? 0 : 1);
}
}
} // namespace dropshell

View File

@ -3,7 +3,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "transwarp.hpp" #include "transwarp.hpp"

View File

@ -45,7 +45,7 @@ struct HelpCommandRegister {
void help_autocomplete(const CommandContext& ctx) { void help_autocomplete(const CommandContext& ctx) {
if (ctx.args.size() == 1) { if (ctx.args.size() == 0) {
// list all commands // list all commands
for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) { for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) {
rawout << cmd << std::endl; rawout << cmd << std::endl;
@ -55,6 +55,11 @@ void help_autocomplete(const CommandContext& ctx) {
} }
void show_command(const std::string& cmd) { void show_command(const std::string& cmd) {
// get console width
int width = get_console_width() - 6; // 5 for [INF] + 1 for space
int firstcol = 34;
int secondcol = width - firstcol - 3;
const auto& cmd_info = CommandRegistry::instance().find_command(cmd); const auto& cmd_info = CommandRegistry::instance().find_command(cmd);
if (!cmd_info) if (!cmd_info)
{ {
@ -62,9 +67,22 @@ void show_command(const std::string& cmd) {
return; return;
} }
info << " "; if (cmd_info->help_usage.length() < width-secondcol)
info << left_align(cmd_info->help_usage, 32); {
info << cmd_info->help_description << std::endl; std::string remaining_description = cmd_info->help_description;
info << " " << left_align(cmd_info->help_usage, firstcol) << get_line_wrap(remaining_description, secondcol);
while (!remaining_description.empty())
info << " " << left_align(" ",firstcol) << get_line_wrap(remaining_description, secondcol-1);
}
else
{
info << " " << cmd_info->help_usage << std::endl;
std::string remaining_description = cmd_info->help_description;
info << " " << left_align(" ",firstcol) << get_line_wrap(remaining_description, secondcol);
while (!remaining_description.empty())
info << " " << left_align(" ",firstcol) << get_line_wrap(remaining_description, secondcol-1);
}
} }
extern const std::string VERSION; extern const std::string VERSION;
@ -106,7 +124,7 @@ int help_handler(const CommandContext& ctx) {
if (ctx.args.size() > 0) if (ctx.args.size() > 0)
return show_command_help(ctx.args[0]); return show_command_help(ctx.args[0]);
info << std::endl; std::cout << std::endl;
maketitle("DropShell version " + VERSION); maketitle("DropShell version " + VERSION);
info << std::endl; info << std::endl;
info << "A tool for managing remote servers, by " << AUTHOR << std::endl; info << "A tool for managing remote servers, by " << AUTHOR << std::endl;
@ -120,16 +138,18 @@ int help_handler(const CommandContext& ctx) {
{ {
// show more! // show more!
show_command("list"); show_command("list");
std::cout << std::endl; info << std::endl;
show_command("install"); show_command("install");
show_command("uninstall"); show_command("uninstall");
show_command("nuke"); show_command("nuke");
std::cout << std::endl; info << std::endl;
show_command("start"); show_command("start");
show_command("stop"); show_command("stop");
std::cout << std::endl; info << std::endl;
show_command("ssh"); show_command("ssh");
std::cout << std::endl; info << std::endl;
show_command("create-server");
show_command("create-service");
} }
return 0; return 0;
} }

View File

@ -5,16 +5,19 @@
#include "templates.hpp" #include "templates.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "utils/hash.hpp" #include "utils/hash.hpp"
#include "autogen/_agent.hpp" #include "autogen/_agent-local.hpp"
#include "autogen/_agent-remote.hpp"
#include "services.hpp" #include "services.hpp"
#include "utils/output.hpp" #include "utils/output.hpp"
#include <fstream>
#include <unistd.h> #include <unistd.h>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include "utils/assert.hpp" #include "utils/assert.hpp"
#include "servers.hpp"
namespace dropshell namespace dropshell
{ {
@ -37,14 +40,14 @@ namespace dropshell
0, // min_args (after command) 0, // min_args (after command)
2, // max_args (after command) 2, // max_args (after command)
"install [SERVER] [SERVICE|all]", "install [SERVER] [SERVICE|all]",
"Install/reinstall host, remote servers, or service(s). Safe/non-destructive way to update.", "Install/reinstall host and remote servers, or service(s). Safe way to update.",
// heredoc // heredoc
R"( R"(
Install components on a server. This is safe to re-run (non-destructive) and used to update Install components on a server. This is safe to re-run (non-destructive) and used to update
servers and their services. servers and their services.
install (re)install dropshell components on this computer. install (re)install dropshell components on this computer, and on all servers.
install SERVER (re)install dropshell agent on the given server. install SERVER (re)install dropshell agent on the particular given server.
install SERVER [SERVICE|all] (re)install the given service (or all services) on the given server. install SERVER [SERVICE|all] (re)install the given service (or all services) on the given server.
Note you need to create the service first with: Note you need to create the service first with:
@ -54,18 +57,33 @@ namespace dropshell
} install_command_register; } install_command_register;
namespace shared_commands
{
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND // install service over ssh : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
bool install_service(const std::string &server, const std::string &service, bool silent) bool install_service(const ServerConfig &server_env, const std::string &service)
{ {
LocalServiceInfo service_info = get_service_info(server, service); std::string server = server_env.get_server_name();
if (!SIvalid(service_info)) LocalServiceInfo service_info = get_service_info(server_env.get_server_name(), service);
return false;
server_env_manager server_env(server); if (!SIvalid(service_info) || !service_info.service_template_hash_match)
if (!server_env.is_valid()) {
warning << "Service " << service << " is using an old template. Updating. " << std::endl;
if (!merge_updated_service_template(server_env.get_server_name(), service))
{
error << "Failed to merge updated service template. " << std::endl;
return false; return false;
}
service_info = get_service_info(server_env.get_server_name(), service);
if (!SIvalid(service_info) || !service_info.service_template_hash_match)
{
error << "Merged updated service template, but it is still not valid. " << std::endl;
return false;
}
}
maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server); maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
@ -84,9 +102,10 @@ namespace dropshell
} }
// Create service directory // Create service directory
std::string remote_service_path = remotepath::service(server, service); std::string user = server_env.get_user_for_service(service);
std::string remote_service_path = remotepath(server,user).service(service);
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path); std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
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))
{ {
std::cerr << "Failed to create service directory " << remote_service_path << std::endl; std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
return false; return false;
@ -94,7 +113,7 @@ namespace dropshell
// Check if rsync is installed on remote host // Check if rsync is installed on remote host
std::string check_rsync_cmd = "which rsync"; std::string check_rsync_cmd = "which rsync";
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent)) if (!execute_ssh_command(server_env.get_SSH_INFO(user), sCommand("", check_rsync_cmd, {}), cMode::Silent))
{ {
std::cerr << "rsync is not installed on the remote host" << std::endl; std::cerr << "rsync is not installed on the remote host" << std::endl;
return false; return false;
@ -102,9 +121,9 @@ namespace dropshell
// Copy template files // Copy template files
debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_template(server, service) << "/" << std::endl; << std::string(8, ' ') << "[REMOTE] " << remotepath(server,user).service_template(service) << "/" << std::endl;
if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service), if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath(server,user).service_template(service),
server_env, silent)) server_env, false, service_info.user))
{ {
std::cerr << "Failed to copy template files using rsync" << std::endl; std::cerr << "Failed to copy template files using rsync" << std::endl;
return false; return false;
@ -112,9 +131,9 @@ namespace dropshell
// Copy service files // Copy service files
debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_config(server, service) << std::endl; << std::string(8, ' ') << "[REMOTE] " << remotepath(server,user).service_config(service) << std::endl;
if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service), if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath(server,user).service_config(service),
server_env, silent)) server_env, false, service_info.user))
{ {
std::cerr << "Failed to copy service files using rsync" << std::endl; std::cerr << "Failed to copy service files using rsync" << std::endl;
return false; return false;
@ -123,14 +142,17 @@ namespace dropshell
// Run install script // Run install script
{ {
info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl; info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, silent, {}); server_env.run_remote_template_command(service, "install", {}, false, {});
} }
// print health tick // print health tick
info << "Health: " << shared_commands::healthtick(server, service) << std::endl; info << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true; return true;
} }
} // namespace shared_commands
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// update_dropshell // update_dropshell
@ -156,6 +178,8 @@ namespace dropshell
int update_dropshell() int update_dropshell()
{ {
maketitle("Updating dropshell on this computer...");
// determine path to this executable // determine path to this executable
std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe"); std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe");
std::filesystem::path parent_path = dropshell_path.parent_path(); std::filesystem::path parent_path = dropshell_path.parent_path();
@ -199,8 +223,8 @@ namespace dropshell
if (currentver >= newver) if (currentver >= newver)
{ {
std::cout << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl; info << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl;
std::cout << "No update needed." << std::endl; info << "Release version is not newer, no update needed." << std::endl;
return 0; return 0;
} }
@ -211,57 +235,78 @@ namespace dropshell
rval = system(bash_script_2.c_str()); rval = system(bash_script_2.c_str());
if (rval != 0) if (rval != 0)
{ {
std::cerr << "Failed to install new version of dropshell." << std::endl; error << "Failed to install new version of dropshell." << std::endl;
return -1; return -1;
} }
std::cout << "Successfully updated " << dropshell_path << " to the latest " << arch << " version." << std::endl; info << "Successfully updated " << dropshell_path << " to the latest " << arch << " version." << std::endl;
// execute the new version // execute the new version
execlp("bash", "bash", "-c", (parent_path / "dropshell").c_str(), "install", (char *)nullptr); execlp("bash", "bash", "-c", (parent_path / "dropshell").c_str(), "install", (char *)nullptr);
std::cerr << "Failed to execute new version of dropshell." << std::endl; error << "Failed to execute new version of dropshell." << std::endl;
return -1; return -1;
} }
int install_local_agent() int install_local_agent()
{ {
std::vector<std::filesystem::path> paths = { maketitle("Installing dropshell agent on this computer...");
gConfig().get_local_template_cache_path(),
gConfig().get_local_backup_path(),
gConfig().get_local_tempfiles_path(),
localpath::agent()};
for (auto &p : gConfig().get_local_server_definition_paths())
paths.push_back(p);
for (auto &p : paths) // clear out old cruft.
if (!std::filesystem::exists(p)) std::filesystem::remove_all(localpath::agent_local());
{ std::filesystem::remove_all(localpath::agent_remote());
std::cout << "Creating directory: " << p << std::endl;
std::filesystem::create_directories(p);
}
// download bb64 for the host architecture. // recreate the directories.
if (!std::filesystem::exists(localpath::agent() + "bb64")) localpath::create_directories();
{
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))
std::cout << "Downloaded local bb64 to " << localpath::agent() << std::endl;
else
std::cerr << "Failed to download local bb64 to " << localpath::agent() << std::endl;
}
else
{
std::cout << "Updating local bb64..." << std::endl;
system((localpath::agent() + "bb64 -u").c_str()); // update.
}
std::cout << "Creating local files to copy to remote agents..." << std::endl; // populate the agent-local directory.
recreate_agent::recreate_tree(localpath::files_for_remote_agent()); recreate_agent_local::recreate_tree(localpath::agent_local());
// run the local agent installer.
execute_local_command(localpath::agent_local(), "agent-install.sh",{}, nullptr, cMode::Defaults | cMode::NoBB64);
// populate the agent-remote directory.
info << "Creating local files to copy to remote agents..." << std::endl;
recreate_agent_remote::recreate_tree(localpath::agent_remote());
return 0; return 0;
} }
int install_server(const ServerConfig &server)
{
// install the dropshell agent on the given server.
maketitle("Installing dropshell agent on " + server.get_server_name(), sColour::INFO);
for (const auto &user : server.get_users())
{
info << "Installing agent for user " << user.user << " on " << server.get_server_name() << std::endl;
std::string agent_path = remotepath(server.get_server_name(),user.user).agent();
ASSERT(agent_path == user.dir+"/agent", "Agent path does not match user directory for "+user.user+"@" + server.get_server_name() + " : " + agent_path + " != " + user.dir);
ASSERT(!agent_path.empty(), "Agent path is empty for " + user.user + "@" + server.get_server_name());
// now create the agent.
// copy across from the local agent files.
info << "Copying local agent files to remote server... " << std::flush;
shared_commands::rsync_tree_to_remote(localpath::agent_remote(), agent_path, server, false, user.user);
info << "done." << std::endl;
// run the agent installer. Can't use BB64 yet, as we're installing it on the remote server.
bool okay = execute_ssh_command(server.get_SSH_INFO(user.user), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr);
if (!okay)
{
error << "ERROR: Failed to install remote agent on " << server.get_server_name() << std::endl;
return 1;
}
info << "Installation on " << server.get_server_name() << " complete." << std::endl;
}
return 0;
}
// ------------------------------------------------------------------------------------------------
// install_host
// ------------------------------------------------------------------------------------------------
int install_host() int install_host()
{ {
// update dropshell. // update dropshell.
@ -275,61 +320,19 @@ namespace dropshell
if (rval != 0) if (rval != 0)
return rval; return rval;
// install the dropshell agent on all servers.
std::vector<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers)
{
rval = install_server(server);
if (rval != 0)
return rval;
}
std::cout << "Installation complete." << std::endl; std::cout << "Installation complete." << std::endl;
return 0; return 0;
} }
int install_server(const std::string &server)
{
// install the dropshell agent on the given server.
maketitle("Installing dropshell agent on " + server, sColour::INFO);
std::string agent_path = remotepath::agent(server);
if (agent_path.empty())
{
error << "Failed to get agent path for " << server << std::endl;
return 1;
}
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Invalid server environment for " << server << std::endl;
return 1;
}
// now create the agent.
// copy across from the local agent files.
info << "Copying local agent files to remote server... " << std::flush;
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);
if (!okay)
{
error << "ERROR: Failed to install remote agent on " << server << std::endl;
return 1;
}
info << "Installation on " << server << " complete." << std::endl;
return 0;
}
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// install command implementation // install command implementation
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -354,15 +357,24 @@ namespace dropshell
} }
// install service(s) // install service(s)
if (!server_exists(server))
{
error << "Server " << server << " does not exist." << std::endl;
info << "Create it with: dropshell create-server " << server << std::endl;
return 1;
}
ServerConfig server_env(server);
ASSERT(server_env.is_valid(), "Invalid server environment for " + server);
if (safearg(ctx.args, 1) == "all") if (safearg(ctx.args, 1) == "all")
{ {
// install all services on the server // install all services on the server
maketitle("Installing all services on " + server); maketitle("Installing all services on " + server);
bool okay = true; bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server); std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services) for (const auto &lsi : services)
{ {
if (!install_service(server, service.service_name, false)) if (!shared_commands::install_service(server_env, lsi.service_name))
okay = false; okay = false;
} }
return okay ? 0 : 1; return okay ? 0 : 1;
@ -370,7 +382,7 @@ namespace dropshell
else else
{ // install the specific service. { // install the specific service.
std::string service = safearg(ctx.args, 1); std::string service = safearg(ctx.args, 1);
return install_service(server, service, false) ? 0 : 1; return shared_commands::install_service(server_env, service) ? 0 : 1;
} }
} }

View File

@ -6,7 +6,7 @@
#include "servers.hpp" #include "servers.hpp"
#include "tableprint.hpp" #include "tableprint.hpp"
#include "transwarp.hpp" #include "transwarp.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include <unistd.h> #include <unistd.h>
@ -81,32 +81,63 @@ void list_servers() {
} }
tableprint tp("All DropShell Servers"); tableprint tp("All DropShell Servers");
tp.add_row({"Name", "User", "Address", "Health", "Ports"}); tp.add_row({"Name", "Address", "User", "Health", "Ports"});
info << "Checking "<<servers.size() << " servers: " << std::flush;
typedef std::map<std::string, shared_commands::ServiceStatus> tServiceStatusMap;
std::vector<tServiceStatusMap> service_status_maps;
typedef struct {dropshell::ServerConfig server; dropshell::UserConfig user;} server_user_pair;
std::vector<server_user_pair> server_user_pairs;
for (const auto& server : servers)
for (const auto& user : server.get_users())
server_user_pairs.push_back({server, user});
// mutex for the tableprint
std::mutex tp_mutex;
info << "Checking "<<server_user_pairs.size() << " agents: " << std::flush;
int checked = 0; int checked = 0;
transwarp::parallel exec{servers.size()};
auto task = transwarp::for_each(exec, servers.begin(), servers.end(), [&](const ServerInfo& server) {
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server.name);
std::set<int> ports_used; transwarp::parallel exec{server_user_pairs.size()};
auto task = transwarp::for_each(exec, server_user_pairs.begin(), server_user_pairs.end(), [&](const server_user_pair& sup) {
ServerConfig server_env(sup.server.get_server_name());
if (!server_env.is_valid())
{
error << "Invalid server environment for " << sup.server.get_server_name() << std::endl;
return;
}
std::string serviceticks = ""; std::string serviceticks = "";
std::string ports_used_str = "";
std::set<int> ports_used;
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(sup.server.get_server_name(),sup.user.user);
for (const auto& [service_name, service_status] : status) { for (const auto& [service_name, service_status] : status) {
ports_used.insert(service_status.ports.begin(), service_status.ports.end()); ports_used.insert(service_status.ports.begin(), service_status.ports.end());
serviceticks += shared_commands::HealthStatus2String(service_status.health) + " "; serviceticks += shared_commands::HealthStatus2String(service_status.health) + " ";
} }
std::string ports_used_str = "";
for (const auto& port : ports_used) for (const auto& port : ports_used)
ports_used_str += std::to_string(port) + " "; ports_used_str += std::to_string(port) + " ";
// critical section
{
std::lock_guard<std::mutex> lock(tp_mutex);
tp.add_row({sup.server.get_server_name(), sup.server.get_SSH_HOST(), sup.user.user, serviceticks, ports_used_str});
tp.add_row({server.name, server.ssh_user, server.ssh_host, serviceticks, ports_used_str});
++checked; ++checked;
// print out a tick character for each server checked. // print out a tick character for each server checked.
info << checked << "" << std::flush; info << checked << "" << std::flush;
}
}); });
task->wait(); task->wait();
info << std::endl << std::endl; info << std::endl << std::endl;
tp.sort({0,2});
tp.print(); tp.print();
} }
@ -114,7 +145,7 @@ void list_servers() {
void show_server_details(const std::string& server_name) { void show_server_details(const std::string& server_name) {
server_env_manager env(server_name); ServerConfig env(server_name);
if (!env.is_valid()) { if (!env.is_valid()) {
error << "Error: Invalid server environment file: " << server_name << std::endl; error << "Error: Invalid server environment file: " << server_name << std::endl;
return; return;
@ -122,37 +153,41 @@ void show_server_details(const std::string& server_name) {
//--------------------- //---------------------
// Check if server is reachable via SSH // Check if server is reachable via SSH
std::string ssh_address = env.get_SSH_HOST(); ASSERT(env.get_users().size() > 0, "No users found for server " + server_name);
std::string ssh_user = env.get_SSH_USER(); sSSHInfo sshinfo = env.get_SSH_INFO(env.get_users()[0].user);
std::string ssh_port = env.get_SSH_PORT(); ASSERT(sshinfo.valid(), "Invalid SSH info for server " + server_name);
if (!ssh_address.empty()) {
info << std::endl << "Server Status:" << std::endl; info << std::endl << "Server Status:" << std::endl;
info << std::string(40, '-') << std::endl; info << std::string(40, '-') << std::endl;
// Try to connect to the server // Try to connect to the server
std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " 'true' 2>/dev/null"; std::string cmd = "ssh -o ConnectTimeout=5 " + sshinfo.get_user() + "@" + sshinfo.get_host() + " -p " + sshinfo.get_port() + " 'true' 2>/dev/null";
int result = system(cmd.c_str()); int result = system(cmd.c_str());
if (result == 0) { if (result == 0) {
info << "Status: Online" << std::endl; info << "Status: Online" << std::endl;
// // Get uptime if possible
// cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null";
// int rval = system(cmd.c_str());
// if (rval != 0) {
// std::cout << "Error: Failed to get uptime" << std::endl;
// }
} else { } else {
warning << "Status: Offline" << std::endl; warning << "Status: Offline" << std::endl;
} }
}
info << std::endl; info << std::endl;
//--------------------- //---------------------
{ {
std::cout << std::endl; std::cout << std::endl;
tableprint tp("Server Configuration: " + server_name, true); tableprint tp("Server Configuration: " + server_name, true);
tp.add_row({"Key", "Value"}); tp.add_row({"Key", "Value"});
for (const auto& [key, value] : env.get_variables()) { for (const auto& [key, value] : env.get_variables()) {
if (key == "SSH_USERS")
{
int i=1;
for (const auto& user : env.get_users())
{
tp.add_row({"USERS -> USER[" + std::to_string(i) + "]", user.user});
tp.add_row({"USERS -> DIR[" + std::to_string(i) + "]", user.dir});
i++;
}
}
else
tp.add_row({key, value}); tp.add_row({key, value});
} }
tp.print(); tp.print();
@ -164,7 +199,6 @@ void show_server_details(const std::string& server_name) {
tableprint tp("Services: " + server_name, false); tableprint tp("Services: " + server_name, false);
tp.add_row({"Status", "Service", "Template","Ports"}); tp.add_row({"Status", "Service", "Template","Ports"});
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server_name); std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server_name);
std::set<int> ports_used; std::set<int> ports_used;
@ -176,7 +210,11 @@ void show_server_details(const std::string& server_name) {
for (const auto& port : service_status.ports) for (const auto& port : service_status.ports)
ports_str += std::to_string(port) + " "; ports_str += std::to_string(port) + " ";
tp.add_row({healthy, service_name, get_service_info(server_name,service_name).template_name, ports_str}); std::string template_name = get_service_info(server_name,service_name).template_name;
if (template_name.empty())
template_name = "Unknown";
tp.add_row({healthy, service_name, template_name, ports_str});
} // end of for (const auto& service : services) } // end of for (const auto& service : services)
tp.print(); tp.print();
} // end of list services } // end of list services

View File

@ -1,149 +0,0 @@
#include "command_registry.hpp"
#include "shared_commands.hpp"
#include "config.hpp"
#include "services.hpp"
#include "server_env_manager.hpp"
#include "utils/directories.hpp"
#include "servers.hpp"
#include "templates.hpp"
#include "utils/utils.hpp"
#include "utils/assert.hpp"
namespace dropshell {
int nuke_handler(const CommandContext& ctx);
static std::vector<std::string> nuke_name_list={"nuke"};
// Static registration
struct NukeCommandRegister {
NukeCommandRegister() {
CommandRegistry::instance().register_command({
nuke_name_list,
nuke_handler,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
2, // min_args (after command)
2, // max_args (after command)
"nuke SERVER SERVICE|all",
"Nuke a service on a server. Destroys everything, both local and remote!",
// heredoc
R"(
Nuke a service.
Examples:
nuke SERVER SERVICE nuke the given service on the given server.
nuke SERVER all nuke all services on the given server.
Note: This command is destructive and will destroy all data and all configuration,
both on the dropshell host and on the remote server.
Use with caution!
)"
});
}
} nuke_command_register;
int nuke_one(std::string server, std::string service)
{
server_env_manager server_env(server);
// step 1 - nuke on remote server.
if (server_env.is_valid())
{
LocalServiceInfo service_info;
service_info = get_service_info(server, service);
if (!SIvalid(service_info))
error << "Warning: Invalid service: " << service << std::endl;
if (server_env.check_remote_dir_exists(remotepath::service(server, service)))
{
// run the nuke script on the remote server if it exists.
// otherwise just uninstall.
if (gTemplateManager().template_command_exists(service_info.template_name, "nuke"))
{
info << "Running nuke script for " << service << " on " << server << std::endl;
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
warning << "Failed to run nuke script: " << service << std::endl;
}
else
{
info << "No nuke script found for " << service << " on " << server << std::endl;
info << "Running uninstall script instead and will clean directories." << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Failed to uninstall service: " << service << std::endl;
}
// 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))
{
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;
}
else
warning << "Failed to remove remote service directory" << std::endl;
}
else
warning << "Service not found on remote server: " << remotepath::service(server, service) << std::endl;
}
else
warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory.
std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{
warning << "Local service directory not found: " << local_service_path << std::endl;
}
else
{
auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0)
error << "Failed to remove local service directory" << std::endl;
}
info << "Nuked service " << service << " on server " << server << std::endl;
return 0;
}
int nuke_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 2, "Usage: nuke SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")");
ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration.");
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
int rval = 0;
// iterate through all service folders in the server directory.
std::string server_path = localpath::server(server);
if (server_path.empty())
{
error << "Server not found: " << server << std::endl;
return 1;
}
for (const auto& entry : std::filesystem::directory_iterator(server_path))
{
if (entry.is_directory() && entry.path().filename().string().find(".") != 0)
{
std::string service_name = entry.path().filename().string();
rval |= nuke_one(server, service_name);
}
}
return rval;
}
else
{
return nuke_one(server, service);
}
}
} // namespace dropshell

View File

@ -0,0 +1,267 @@
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/output.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "command_registry.hpp"
#include "config.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "servers.hpp"
#include "templates.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
namespace dropshell
{
int restoredata_handler(const CommandContext &ctx);
void restoredata_autocomplete(const CommandContext &ctx);
static std::vector<std::string> restoredata_name_list = {"restoredata", "rd", "restore", "rest"};
// Static registration
struct RestoreDataCommandRegister
{
RestoreDataCommandRegister()
{
CommandRegistry::instance().register_command({restoredata_name_list,
restoredata_handler,
restoredata_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
3, // min_args (after command)
3, // max_args (after command)
"restoredata SERVER SERVICE BACKUP_FILE|latest",
"Restore data for a service on a server, overwriting the existing data.",
// heredoc
R"(
restoredata SERVER SERVICE BACKUP_FILE Restore data to a service on a server. Destructive.
restoredata SERVER SERVICE latest Restore the latest backup for the given service.
Note: This command will not create any service configuration, you need
to have a valid service installed first.
The backup file must be in the local backups directory.
WARNING: This will permanently overwrite the service's data on the remote server!
)"});
}
} restoredata_command_register;
std::vector<shared_commands::cBackupFileName> get_backup_files(const std::string &server, const std::string &match_service = "", const std::string &match_template_name = "")
{
std::string local_backups_dir = localpath::backups();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return {};
}
std::vector<shared_commands::cBackupFileName> backups;
for (const auto &entry : std::filesystem::directory_iterator(local_backups_dir))
{
if (!entry.is_regular_file())
continue;
std::string filename = entry.path().filename().string();
shared_commands::cBackupFileName backup_details(filename);
if (backup_details.is_valid())
if (match_service.empty() || backup_details.get_service() == match_service)
if (match_template_name.empty() || backup_details.get_template_name() == match_template_name)
backups.push_back(backup_details);
}
// sort backups by datetime
std::sort(backups.begin(), backups.end(), [](const shared_commands::cBackupFileName &a, const shared_commands::cBackupFileName &b)
{ return a.get_datetime() > b.get_datetime(); });
return backups;
}
int restoredata_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 3, "Invalid number of arguments");
std::string server = ctx.args[0];
std::string service = ctx.args[1];
std::string backup_arg = ctx.args[2];
ServerConfig server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return 1;
}
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
{
error << "Service " << service << " is not valid" << std::endl;
return 1;
}
std::optional<shared_commands::cBackupFileName> backup_details;
if (backup_arg == "latest")
{ // special case.
std::vector<shared_commands::cBackupFileName> backups = get_backup_files(server, service, service_info.template_name); // this service only (and also match template in case something changed there!).
if (backups.empty())
{
error << "No backups found for " << server << "/" << service << std::endl;
debug << "Template also has to match with the service template: " << service_info.template_name << std::endl;
return 1;
}
backup_details = backups[0];
} else {
backup_details = shared_commands::cBackupFileName(backup_arg);
if (!backup_details->is_valid())
{
error << "Invalid backup file: " << backup_arg << std::endl;
return 1;
}
}
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 = localpath::backups();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir))
{
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return 1;
}
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_details->get_filename()).string();
if (!std::filesystem::exists(local_backup_file_path))
{
error << "Error: Backup file not found at " << local_backup_file_path << std::endl;
return 1;
}
if (backup_details->get_template_name() != service_info.template_name)
{
error << "Error: Backup template does not match service template. Can't restore." << std::endl;
info << "Backup template: " << backup_details->get_template_name() << std::endl;
info << "Service template: " << service_info.template_name << std::endl;
return 1;
}
warning << "*** ALL DATA FOR " << server << "/" << service << " WILL BE OVERWRITTEN! ***" << std::endl;
// run the restore script
info << "OK, here goes..." << std::endl;
{ // backup existing service
info << "1) Backing up old service... " << std::endl;
if (!shared_commands::backupdata_service(server, service))
{
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;
}
{ // nuke the old service
info << "2) Nuking old service..." << std::endl;
if (!shared_commands::nuke_service(server, service))
return 1;
}
{ // create the new service
info << "3) Creating new service..." << std::endl;
if (!shared_commands::create_service(server, service_info.template_name, service, service_info.user))
return 1;
}
{ // installing fresh service
info << "4) Install of fresh service..." << std::endl;
ServerConfig server_env(server);
if (!shared_commands::install_service(server_env, service))
return 1;
}
{ // restore service from backup
info << "5) Restoring service data from backup..." << std::endl;
std::string user = server_env.get_user_for_service(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;
// Copy backup file from local to server
if (!shared_commands::scp_file_to_remote(server_env, local_backup_file_path, remote_backup_file_path, false, service_info.user))
{
error << "Failed to copy backup file from local to server" << std::endl;
return 1;
}
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;
debug << " TEMP_DIR: " << remote_temp_folder.path() << std::endl;
server_env.run_remote_template_command(service, "restore", {}, false, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}});
} // dtor of remote_temp_folder will clean up the temp folder on the server
{ // healthcheck the service
info << "5) Healthchecking service..." << std::endl;
std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m";
bool healthy = (server_env.run_remote_template_command(service, "status", {}, false, {}));
info << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
}
return 0;
}
void restoredata_autocomplete(const CommandContext &ctx)
{
shared_commands::std_autocomplete(ctx);
if (ctx.args.size() == 2) // next arg is the backup file
{
std::string server = ctx.args[0];
std::string service = ctx.args[1];
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
{
error << "Service " << service << " is not valid" << std::endl;
return;
}
std::string template_name = service_info.template_name;
std::vector<shared_commands::cBackupFileName> backups = get_backup_files(server, "", template_name); // any service, but must match template.
// print most recent backup for each {host,service} pair
std::map<std::string, std::string> unique_backups;
for (const auto &backup : backups)
{
std::string key = backup.get_server() + "-" + backup.get_service();
if (unique_backups.find(key) == unique_backups.end())
unique_backups[key] = backup.get_filename();
}
for (const auto &[key, value] : unique_backups)
rawout << value << std::endl;
}
}
} // namespace dropshell

View File

@ -1,7 +1,7 @@
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "utils/assert.hpp" #include "utils/assert.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "directories.hpp" #include "directories.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
@ -21,10 +21,10 @@ namespace dropshell
if (ctx.args.size() == 0) if (ctx.args.size() == 0)
{ // just the command, no args yet. { // just the command, no args yet.
// list servers // list servers
std::vector<ServerInfo> servers = get_configured_servers(); std::vector<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
rawout << server.name << std::endl; rawout << server.get_server_name() << std::endl;
} }
} }
else if (ctx.args.size() == 1) else if (ctx.args.size() == 1)
@ -54,14 +54,15 @@ namespace dropshell
bool rsync_tree_to_remote( bool rsync_tree_to_remote(
const std::string &local_path, const std::string &local_path,
const std::string &remote_path, const std::string &remote_path,
server_env_manager &server_env, const ServerConfig &server_env,
bool silent) bool silent,
std::string user)
{ {
ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync."); ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync.");
std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " + std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " +
quote(local_path + "/") + " " + quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(user + "@" + server_env.get_SSH_HOST() + ":" +
remote_path + "/"); remote_path + "/");
return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults)); return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} }
@ -84,11 +85,12 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// cRemoteTempFolder : SHARED CLASS // cRemoteTempFolder : SHARED CLASS
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env) cRemoteTempFolder::cRemoteTempFolder(const ServerConfig &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); 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; error << "Failed to create temp directory on server" << std::endl;
else else
mPath = p; mPath = p;
@ -97,7 +99,7 @@ namespace dropshell
cRemoteTempFolder::~cRemoteTempFolder() cRemoteTempFolder::~cRemoteTempFolder()
{ {
std::string rm_cmd = "rm -rf " + quote(mPath); 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 std::string cRemoteTempFolder::path() const
@ -108,22 +110,25 @@ namespace dropshell
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// get_all_services_status : SHARED COMMAND // get_all_services_status : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name) std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env)
{ {
std::map<std::string, ServiceStatus> status; std::map<std::string, ServiceStatus> status;
for (const auto& user : server_env.get_users()) {
server_env_manager env(server_name); status.merge(get_all_services_status(server_env, user.user));
if (!env.is_valid()) }
{
error << "Invalid server environment" << std::endl;
return status; return status;
} }
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env, std::string user)
{
std::map<std::string, ServiceStatus> status;
std::string server_name = server_env.get_server_name();
std::string output; std::string output;
if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), std::string agentpath = remotepath(server_name,user).agent();
"./_allservicesstatus.sh", if (!execute_ssh_command(server_env.get_SSH_INFO(user),
{{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}), sCommand(agentpath, "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", agentpath}}),
cMode::CaptureOutput | cMode::Silent, cMode::Silent,
&output)) &output))
return status; return status;
@ -164,11 +169,10 @@ namespace dropshell
} }
} }
} }
return status; return status;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// healthtick : SHARED COMMAND // healthtick : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -207,26 +211,27 @@ namespace dropshell
return ":error:"; return ":error:";
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// is_healthy : SHARED COMMAND // is_healthy : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
HealthStatus is_healthy(const std::string &server, const std::string &service) HealthStatus is_healthy(const std::string &server, const std::string &service)
{ {
server_env_manager env(server); ServerConfig env(server);
if (!env.is_valid()) if (!env.is_valid())
{ {
error << "Server service not initialized" << std::endl; error << "Server service not initialized" << std::endl;
return HealthStatus::ERROR; return HealthStatus::ERROR;
} }
if (!env.check_remote_dir_exists(remotepath::service(server, service))) std::string user = env.get_user_for_service(service);
if (!env.check_remote_dir_exists(remotepath(server,user).service(service), user))
{ {
return HealthStatus::NOTINSTALLED; return HealthStatus::NOTINSTALLED;
} }
std::string script_path = remotepath::service_template(server, service) + "/status.sh"; std::string script_path = remotepath(server,user).service_template(service) + "/status.sh";
if (!env.check_remote_file_exists(script_path)) if (!env.check_remote_file_exists(script_path, user))
{ {
return HealthStatus::UNKNOWN; return HealthStatus::UNKNOWN;
} }
@ -237,7 +242,6 @@ namespace dropshell
return HealthStatus::HEALTHY; return HealthStatus::HEALTHY;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// healthmark : SHARED COMMAND // healthmark : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -247,6 +251,91 @@ namespace dropshell
return HealthStatus2String(status); return HealthStatus2String(status);
} }
// ------------------------------------------------------------------------------------------------
// cBackupFileName : SHARED CLASS
// ------------------------------------------------------------------------------------------------
cBackupFileName::cBackupFileName(const std::string &server, const std::string &service, const std::string &template_name)
{
mServer = server;
mService = service;
mTemplateName = template_name;
// Get current datetime for backup filename
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::stringstream datetime;
datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
mDatetime = datetime.str();
}
cBackupFileName::cBackupFileName(const std::string &filename)
{
// Parse the filename according to the format:
// server + magic_string() + template_name + magic_string() + service + magic_string() + datetime + ".tgz"
std::string name = filename;
if (name.size() > 4 && name.substr(name.size() - 4) == ".tgz")
name = name.substr(0, name.size() - 4);
std::string sep = magic_string();
size_t first = name.find(sep);
size_t second = name.find(sep, first + sep.size());
size_t third = name.find(sep, second + sep.size());
if (first == std::string::npos || second == std::string::npos || third == std::string::npos)
{
mServer = mService = mTemplateName = mDatetime = "";
return;
}
mServer = name.substr(0, first);
mTemplateName = name.substr(first + sep.size(), second - (first + sep.size()));
mService = name.substr(second + sep.size(), third - (second + sep.size()));
mDatetime = name.substr(third + sep.size());
}
std::string cBackupFileName::get_filename() const
{
return mServer + magic_string() + mTemplateName + magic_string() + mService + magic_string() + mDatetime + ".tgz";
}
std::string cBackupFileName::get_server() const { return mServer; }
std::string cBackupFileName::get_service() const { return mService; }
std::string cBackupFileName::get_template_name() const { return mTemplateName; }
std::string cBackupFileName::get_datetime() const { return mDatetime; }
bool cBackupFileName::is_valid() const
{
// All fields must be non-empty, and none may contain the magic string
return !mServer.empty() && !mService.empty() && !mTemplateName.empty() && !mDatetime.empty() &&
!has_magic_string(mServer) && !has_magic_string(mService) && !has_magic_string(mTemplateName);
}
// ------------------------------------------------------------------------------------------------
// scp_file_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user)
{
if (!server_env.is_valid())
{
error << "Invalid server environment" << std::endl;
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) + " " + 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));
}
// ------------------------------------------------------------------------------------------------
// scp_file_from_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user)
{
if (!server_env.is_valid())
{
error << "Invalid server environment" << std::endl;
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() + " " + 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));
}
} // namespace shared_commands } // namespace shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -3,7 +3,7 @@
#include "servers.hpp" #include "servers.hpp"
#include "command_registry.hpp" #include "command_registry.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
namespace dropshell namespace dropshell
{ {
@ -31,23 +31,26 @@ namespace dropshell
class cRemoteTempFolder class cRemoteTempFolder
{ {
public: public:
cRemoteTempFolder(const server_env_manager &server_env); // create a temp folder on the remote server cRemoteTempFolder(const ServerConfig &server_env, std::string user); // create a temp folder on the remote server
~cRemoteTempFolder(); // delete the 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 std::string path() const; // get the path to the temp folder on the remote server
private: private:
std::string mPath; std::string mPath;
const server_env_manager &mServerEnv; const ServerConfig &mServerEnv;
std::string mUser;
}; };
bool rsync_tree_to_remote( bool rsync_tree_to_remote(
const std::string &local_path, const std::string &local_path,
const std::string &remote_path, const std::string &remote_path,
server_env_manager &server_env, const ServerConfig &server_env,
bool silent); bool silent,
std::string user);
std::string get_arch(); std::string get_arch();
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name); std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env);
std::map<std::string, ServiceStatus> get_all_services_status(const ServerConfig & server_env, std::string user);
std::string healthtick(const std::string &server, const std::string &service); std::string healthtick(const std::string &server, const std::string &service);
std::string HealthStatus2String(HealthStatus status); std::string HealthStatus2String(HealthStatus status);
@ -57,6 +60,45 @@ namespace dropshell
void std_autocomplete(const CommandContext &ctx); void std_autocomplete(const CommandContext &ctx);
void std_autocomplete_allowall(const CommandContext &ctx); void std_autocomplete_allowall(const CommandContext &ctx);
class cBackupFileName
{
public:
cBackupFileName(const std::string &server, const std::string &service, const std::string &template_name);
cBackupFileName(const std::string &filename);
std::string get_filename() const;
std::string get_server() const;
std::string get_service() const;
std::string get_template_name() const;
std::string get_datetime() const;
bool is_valid() const;
private:
std::string mServer;
std::string mService;
std::string mTemplateName;
std::string mDatetime;
};
bool scp_file_to_remote(const ServerConfig &server_env, const std::string &local_path, const std::string &remote_path, bool silent, std::string user);
bool scp_file_from_remote(const ServerConfig &server_env, const std::string &remote_path, const std::string &local_path, bool silent, std::string user);
// defined in backupdata.cpp, used by restoredata.cpp.
bool backupdata_service(const ServerConfig &server_env, const std::string& service);
// defined in uninstall.cpp
bool uninstall_service(const ServerConfig &server_env, const std::string &service);
// defined in nuke.cpp
bool nuke_service(const std::string &server, const std::string &service);
// defined in install.cpp
bool install_service(const ServerConfig &server_env, const std::string &service);
// defined in create-service.cpp
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override="");
bool merge_updated_service_template(const std::string &server_name, const std::string &service_name);
} // namespace shared_commands } // namespace shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -3,10 +3,11 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "assert.hpp"
namespace dropshell namespace dropshell
{ {
@ -34,27 +35,26 @@ namespace dropshell
ssh SERVER SERVICE SSH into a docker container for a service. ssh SERVER SERVICE SSH into a docker container for a service.
ssh SERVER SSH into a server. ssh SERVER SSH into a server.
ssh USER@SERVER SSH into a server as a specific user.
)"}); )"});
} }
} ssh_command_register; } ssh_command_register;
bool ssh_into_server(const std::string &server, std::string user)
bool ssh_into_server(const std::string &server)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; 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; return true;
} }
bool ssh_into_service(const std::string &server, const std::string &service) bool ssh_into_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
@ -80,7 +80,7 @@ namespace dropshell
return false; 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; return true;
} }
@ -92,18 +92,41 @@ namespace dropshell
return 1; return 1;
} }
std::string server = safearg(ctx.args, 0); // ssh into the server
if (ctx.args.size() < 2) if (ctx.args.size() < 2)
{ {
// ssh into the server std::string arg1 = safearg(ctx.args, 0);
return ssh_into_server(server) ? 0 : 1; 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.
ServerConfig 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); std::string service = safearg(ctx.args, 1);
// ssh into the specific service.
return ssh_into_service(server, service) ? 0 : 1; return ssh_into_service(server, service) ? 0 : 1;
} }
}
} // namespace dropshell } // namespace dropshell

View File

@ -3,7 +3,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp" #include "utils/output.hpp"
@ -44,7 +44,7 @@ namespace dropshell
bool start_service(const std::string &server, const std::string &service) bool start_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;

View File

@ -3,7 +3,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp" #include "utils/output.hpp"
@ -44,7 +44,7 @@ namespace dropshell
bool stop_service(const std::string &server, const std::string &service) bool stop_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server); ServerConfig server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
error << "Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;

View File

@ -41,45 +41,40 @@ namespace dropshell
} }
} uninstall_command_register; } uninstall_command_register;
bool uninstall_service(const std::string &server, const std::string &service, bool silent = false) namespace shared_commands {
bool uninstall_service(const ServerConfig & server_env, const std::string &service)
{ {
if (!silent) ASSERT(server_env.is_valid(), "Invalid server environment for " + server_env.get_server_name());
std::string server = server_env.get_server_name();
maketitle("Uninstalling " + service + " on " + server); maketitle("Uninstalling " + service + " on " + server);
server_env_manager server_env(server); std::string user = server_env.get_user_for_service(service);
if (!server_env.is_valid())
{
error << "Invalid server: " << server << std::endl;
return false; // should never hit this.
}
// 2. Check if service directory exists on server // 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; error << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall return true; // Nothing to uninstall
} }
// 3. Run uninstall script if it exists // 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", {}, silent, {})) if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
if (!silent)
warning << "Uninstall script failed, but continuing with directory removal" << std::endl; 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. // 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), silent)) 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"); ASSERT(!server_env.check_remote_dir_exists(remotepath(server, user).service(service), user), "Service directory still found on server after uninstall");
if (!silent) info << "Removed remote service directory " << remotepath(server, user).service(service) << std::endl;
info << "Removed remote service directory " << remotepath::service(server, service) << std::endl;
} }
else if (!silent) else
warning << "Failed to remove remote service directory" << std::endl; warning << "Failed to remove remote service directory" << std::endl;
if (!silent)
info << "Completed service " << service << " uninstall on " << server << std::endl; info << "Completed service " << service << " uninstall on " << server << std::endl;
return true; return true;
} }
} // namespace shared_commands
int uninstall_handler(const CommandContext &ctx) int uninstall_handler(const CommandContext &ctx)
{ {
@ -98,14 +93,14 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server); std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services) for (const auto &service : services)
{ {
if (!uninstall_service(server, service.service_name)) if (!shared_commands::uninstall_service(server, service.service_name))
okay = false; okay = false;
} }
return okay ? 0 : 1; return okay ? 0 : 1;
} }
std::string service = safearg(ctx.args, 1); std::string service = safearg(ctx.args, 1);
return uninstall_service(server, service) ? 0 : 1; return shared_commands::uninstall_service(server, service) ? 0 : 1;
} }
} // namespace dropshell } // namespace dropshell

View File

@ -3,9 +3,11 @@
#include <fstream> #include <fstream>
#include "config.hpp" #include "config.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/json.hpp"
#include <filesystem> #include <filesystem>
#include "utils/execute.hpp" #include "utils/execute.hpp"
#include "output.hpp"
namespace dropshell { namespace dropshell {
@ -43,6 +45,15 @@ bool config::load_config() { // load json config file.
return true; return true;
} }
void _append(std::vector<std::string> & a, const std::vector<std::string> & b) {
if (b.empty())
return;
if (a.empty())
a = b;
else
a.insert(std::end(a), std::begin(b), std::end(b));
}
bool config::save_config(bool create_aux_directories) bool config::save_config(bool create_aux_directories)
{ {
std::string config_path = localfile::dropshell_json(); std::string config_path = localfile::dropshell_json();
@ -58,37 +69,27 @@ bool config::save_config(bool create_aux_directories)
if (!mIsConfigSet) if (!mIsConfigSet)
{ {
std::string homedir = localpath::current_user_home(); std::string homedir = localpath::current_user_home();
std::string dropshell_base = homedir + "/.dropshell"; std::string dropshell_base = homedir + "/.local/dropshell_files";
mConfig["tempfiles"] = dropshell_base + "/tmp";
mConfig["backups"] = dropshell_base + "/backups";
mConfig["template_cache"] = dropshell_base + "/template_cache";
mConfig["template_registry_URLs"] = {
"https://templates.dropshell.app"
};
mConfig["template_local_paths"] = {
dropshell_base + "/local_templates"
};
mConfig["server_definition_paths"] = { mConfig["server_definition_paths"] = {
dropshell_base + "/servers" dropshell_base + "/servers"
}; };
mConfig["template_upload_registry_url"] = "https://templates.dropshell.app"; mConfig["template_local_paths"] = {
mConfig["template_upload_registry_token"] = "SECRETTOKEN"; dropshell_base + "/local_templates"
};
mConfig["template_registry_URLs"] = {
"https://templates.dropshell.app"
};
mConfig["template_upload_token"] = "SECRETTOKEN";
} }
config_file << mConfig.dump(4); config_file << mConfig.dump(4);
config_file.close(); config_file.close();
if (create_aux_directories) { if (create_aux_directories) {
std::vector<std::filesystem::path> paths = { std::vector<std::string> paths;
get_local_template_cache_path(), _append(paths, get_local_template_paths());
get_local_backup_path(), _append(paths, get_local_server_definition_paths());
get_local_tempfiles_path()
};
for (auto & p : get_local_server_definition_paths())
paths.push_back(p);
for (auto & p : paths) for (auto & p : paths)
if (!std::filesystem::exists(p)) if (!std::filesystem::exists(p))
{ {
@ -97,6 +98,11 @@ bool config::save_config(bool create_aux_directories)
} }
} }
debug << "Config paths: " << std::endl;
for (auto [key,value] : mConfig.items()) {
debug << " " << key << ": " << value << std::endl;
}
return true; return true;
} }
@ -107,31 +113,20 @@ bool config::is_config_set() const
bool config::is_agent_installed() bool config::is_agent_installed()
{ {
return std::filesystem::exists(localpath::agent() + "/bb64"); return std::filesystem::exists(localfile::bb64());
}
std::string config::get_local_tempfiles_path() {
return mConfig["tempfiles"];
}
std::string config::get_local_backup_path() {
return mConfig["backups"];
}
std::string config::get_local_template_cache_path() {
return mConfig["template_cache"];
} }
std::vector<std::string> config::get_template_registry_urls() { std::vector<std::string> config::get_template_registry_urls() {
nlohmann::json template_registry_urls = mConfig["template_registry_URLs"]; nlohmann::json template_registry_urls = mConfig["template_registry_URLs"];
std::vector<std::string> urls; std::vector<std::string> urls;
for (auto &url : template_registry_urls) { for (auto &url : template_registry_urls) {
if (url.is_string() && !url.empty())
urls.push_back(url); urls.push_back(url);
} }
return urls; return urls;
} }
std::vector<std::string> config::get_template_local_paths() std::vector<std::string> config::get_local_template_paths()
{ {
nlohmann::json template_local_paths = mConfig["template_local_paths"]; nlohmann::json template_local_paths = mConfig["template_local_paths"];
std::vector<std::string> paths; std::vector<std::string> paths;
@ -144,23 +139,40 @@ std::vector<std::string> config::get_template_local_paths()
std::vector<std::string> config::get_local_server_definition_paths() { std::vector<std::string> config::get_local_server_definition_paths() {
nlohmann::json server_definition_paths = mConfig["server_definition_paths"]; nlohmann::json server_definition_paths = mConfig["server_definition_paths"];
std::vector<std::string> paths; std::vector<std::string> paths;
for (auto &path : server_definition_paths) { for (auto &path : server_definition_paths) {
if (path.is_string() && !path.empty()) if (path.is_string() && !path.empty())
paths.push_back(path); paths.push_back(path);
else
std::cerr << "Warning: Invalid server definition path: " << path << std::endl;
} }
return paths; return paths;
} }
std::string config::get_template_upload_registry_url() { std::string config::get_server_create_path()
return mConfig["template_upload_registry_url"]; {
std::vector<std::string> paths = get_local_server_definition_paths();
if (paths.empty())
return "";
return paths[0];
} }
std::string config::get_template_upload_registry_token() { std::string config::get_template_create_path()
return mConfig["template_upload_registry_token"]; {
std::vector<std::string> paths = get_local_template_paths();
if (paths.empty())
return "";
return paths[0];
}
std::string config::get_template_upload_url()
{
std::vector<std::string> urls = get_template_registry_urls();
if (urls.empty())
return "";
return urls[0];
}
std::string config::get_template_upload_token() {
return mConfig["template_upload_token"];
} }
} // namespace dropshell } // namespace dropshell

View File

@ -2,7 +2,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "utils/json.hpp"
#define JSON_INLINE_ALL
#include "json.hpp"
namespace dropshell { namespace dropshell {
@ -17,15 +19,14 @@ class config {
bool is_config_set() const; bool is_config_set() const;
static bool is_agent_installed(); static bool is_agent_installed();
std::string get_local_tempfiles_path();
std::string get_local_backup_path();
std::string get_local_template_cache_path();
std::vector<std::string> get_template_registry_urls(); std::vector<std::string> get_template_registry_urls();
std::vector<std::string> get_template_local_paths(); std::vector<std::string> get_local_template_paths();
std::vector<std::string> get_local_server_definition_paths(); std::vector<std::string> get_local_server_definition_paths();
std::string get_template_upload_registry_url(); std::string get_server_create_path();
std::string get_template_upload_registry_token(); std::string get_template_create_path();
std::string get_template_upload_url();
std::string get_template_upload_token();
private: private:
nlohmann::json mConfig; nlohmann::json mConfig;

View File

@ -1,231 +0,0 @@
#include "server_env_manager.hpp"
#include "utils/directories.hpp"
#include "utils/utils.hpp"
#include "services.hpp"
#include "templates.hpp"
#include "utils/utils.hpp"
#include "utils/json.hpp"
#include "utils/execute.hpp"
#include <iostream>
#include <memory>
#include <filesystem>
#include <fstream>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <iostream>
#include <wordexp.h> // For potential shell-like expansion if needed
namespace dropshell {
server_env_manager::server_env_manager(const std::string& server_name) : mValid(false), mServerName(server_name) {
if (server_name.empty())
return;
// Construct the full path to server.env
std::string server_env_path = localfile::server_json(server_name);
// Check if file exists
if (!std::filesystem::exists(server_env_path)) {
std::cerr << "Server environment file not found: " + server_env_path << " for server " << server_name << std::endl;
return;
}
try {
// Use envmanager to handle the environment file
nlohmann::json server_env_json = nlohmann::json::parse(std::ifstream(server_env_path));
if (server_env_json.empty()) {
std::cerr << "Error: Failed to parse server environment file: " + server_env_path << std::endl;
return;
}
// get the variables from the json
for (const auto& var : server_env_json.items()) {
std::string value;
if (var.value().is_string())
value = var.value();
else if (var.value().is_number_integer())
value = std::to_string(var.value().get<int>());
else if (var.value().is_boolean())
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"}) {
if (mVariables.find(var) == mVariables.end()) {
// Print the variables identified in the file
std::cout << "Variables identified in the file:" << std::endl;
for (const auto& v : mVariables) {
std::cout << " " << v.first << std::endl;
}
throw std::runtime_error("Missing required variable: " + std::string(var));
}
}
mValid = true;
} catch (const std::exception& e) {
std::cerr << "Failed to parse server environment file: " + std::string(e.what()) << std::endl;
}
}
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)
{
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;
try {
std::ofstream server_env_file(server_env_path);
server_env_file << server_env_json.dump(4);
server_env_file.close();
return true;
} catch (const std::exception& e) {
std::cerr << "Failed to create server environment file: " + std::string(e.what()) << std::endl;
return false;
}
}
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<sCommand> server_env_manager::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const
{
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<std::string, std::string> 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)));
}
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;
}
bool server_env_manager::check_remote_dir_exists(const std::string &dir_path) const
{
sCommand scommand("", "test -d " + quote(dir_path),{});
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
}
bool server_env_manager::check_remote_file_exists(const std::string& file_path) const {
sCommand scommand("", "test -f " + quote(file_path),{});
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
}
bool server_env_manager::check_remote_items_exist(const std::vector<std::string> &file_paths) const
{
// convert file_paths to a single string, separated by spaces
std::string file_paths_str;
std::string file_names_str;
for (const auto& file_path : file_paths) {
file_paths_str += quote(file_path) + " ";
file_names_str += std::filesystem::path(file_path).filename().string() + " ";
}
// 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);
if (!okay) {
std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl;
return false;
}
return true;
}
bool server_env_manager::remove_remote_dir(const std::string &dir_path, bool silent) const
{
std::filesystem::path path(dir_path);
std::filesystem::path parent_path = path.parent_path();
std::string target_dir = path.filename().string();
if (parent_path.empty())
parent_path="/";
if (target_dir.empty())
return false;
if (!silent)
std::cout << "Removing remote directory " << target_dir << " in " << parent_path << " on " << mServerName << std::endl;
std::string remote_cmd =
"docker run --rm -v " + quote(parent_path.string()) + ":/parent " +
" alpine rm -rf \"/parent/" + target_dir + "\"";
// if (!silent)
// std::cout << "Running command: " << remote_cmd << std::endl;
sCommand scommand("", remote_cmd,{});
cMode mode = (silent ? cMode::Silent : cMode::Defaults);
return execute_ssh_command(get_SSH_INFO(), scommand, mode);
}
bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const
{
auto scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
for (const auto& [key, value] : extra_env_vars)
scommand->add_env_var(key, value);
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);
}
bool server_env_manager::run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command, std::vector<std::string> args, std::string &output, bool silent, std::map<std::string, std::string> extra_env_vars) const
{
auto scommand = construct_standard_template_run_cmd(service_name, command, args, false);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
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);
}
// base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh"
// echo YmFzaCAtYyAnRk9PPUJBUiBXSEVFPVlBWSBiYXNoIC4vdGVzdC5zaCcK | base64 -d | bash
} // namespace dropshell

View File

@ -1,77 +0,0 @@
// server_env.hpp
//
// read the server.env file and provide a class to access the variables
#ifndef __SERVER_ENV_HPP
#define __SERVER_ENV_HPP
#include <string>
#include <map>
#include <memory>
#include <vector>
#include "utils/execute.hpp"
#include <optional>
namespace dropshell {
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_PORT
// the following replacements are made in the values:
// ${USER} -> the username of the user running dropshell
class server_env_manager {
public:
server_env_manager(const std::string& server_name);
static bool 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);
std::string get_variable(const std::string& name) const;
// trivial getters.
const std::map<std::string, std::string>& 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()}; }
bool is_valid() const { return mValid; }
std::string get_server_name() const { return mServerName; }
// 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<std::string>& file_paths) const;
bool remove_remote_dir(const std::string &dir_path, bool silent) const;
bool run_remote_template_command(const std::string& service_name, const std::string& command,
std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const;
bool run_remote_template_command_and_capture_output(const std::string& service_name, const std::string& command,
std::vector<std::string> args, std::string & output, bool silent, std::map<std::string, std::string> extra_env_vars) const;
private:
std::optional<sCommand> construct_standard_template_run_cmd(const std::string& service_name, const std::string& command, const std::vector<std::string> args, const bool silent) const;
private:
std::string mServerName;
std::map<std::string, std::string> mVariables;
bool mValid;
};
} // namespace dropshell
#endif // __SERVER_ENV_HPP

View File

@ -1,91 +1,404 @@
#include "servers.hpp"
#include "server_env_manager.hpp"
#include "utils/tableprint.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "utils/utils.hpp"
#include "servers.hpp"
#include "services.hpp" #include "services.hpp"
#include "config.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "contrib/transwarp.hpp" #include "utils/utils.hpp"
#include "utils/execute.hpp"
#include "output.hpp"
#include "utils/assert.hpp"
#include "config.hpp"
#include <iostream> #include <iostream>
#include <fstream> #include <memory>
#include <iomanip>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <iostream>
#include <wordexp.h> // For potential shell-like expansion if needed
namespace dropshell { namespace dropshell
{
std::vector<ServerInfo> get_configured_servers() { ServerConfig::ServerConfig(const std::string &server_name) : mValid(false), mServerName(server_name)
std::vector<ServerInfo> servers; {
if (server_name.empty())
return;
std::string server_json_path = localfile::server_json(server_name);
// Check if file exists
if (!std::filesystem::exists(server_json_path))
{
std::cerr << "Server environment file not found: " + server_json_path << " for server " << server_name << std::endl;
return;
}
try
{
// Use envmanager to handle the environment file
nlohmann::json server_env_json = nlohmann::json::parse(std::ifstream(server_json_path));
if (server_env_json.empty())
{
error << "Failed to parse server environment file at "<< server_json_path << std::endl;
info << "The returned json was empty." << std::endl;
return;
}
// get the variables from the json, converting everything to strings.
for (const auto &var : server_env_json.items())
{
std::string value;
if (var.value().is_string())
value = var.value();
else if (var.value().is_number_integer())
value = std::to_string(var.value().get<int>());
else if (var.value().is_boolean())
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_PORT", "SSH_USERS"})
{
if (mVariables.find(var) == mVariables.end())
{
// Print the variables identified in the file
info << "Variables identified in the file:" << std::endl;
for (const auto &v : mVariables)
{
info << " " << v.first << std::endl;
}
throw std::runtime_error("Missing required variable: " + std::string(var));
}
}
// Parse users array
if (!server_env_json.contains("SSH_USERS") || !server_env_json["SSH_USERS"].is_array())
{
error << "SSH_USERS array not found or invalid in server configuration" << std::endl;
return;
}
for (const auto &user_json : server_env_json["SSH_USERS"])
{
UserConfig user;
user.user = user_json["USER"].get<std::string>();
user.dir = user_json["DIR"].get<std::string>();
mUsers.push_back(user);
}
if (mUsers.empty())
{
error << "No users defined in server configuration " << server_json_path << std::endl;
return;
}
mValid = true;
}
catch (const std::exception &e)
{
error << "Failed to parse " << server_json_path << std::endl;
error << "Error: " << e.what() << std::endl;
mValid = false;
}
}
std::string ServerConfig::get_SSH_HOST() const
{
return get_variable("SSH_HOST");
}
std::string ServerConfig::get_SSH_PORT() const
{
return get_variable("SSH_PORT");
}
std::vector<UserConfig> ServerConfig::get_users() const
{
return mUsers;
}
std::string ServerConfig::get_user_dir(const std::string &user) const
{
for (const auto &u : mUsers)
{
if (u.user == user)
{
return u.dir;
}
}
return "";
}
std::string ServerConfig::get_server_name() const
{
return mServerName;
}
std::string ServerConfig::get_user_for_service(const std::string &service) const
{
return dropshell::get_user_for_service(mServerName, service);
}
std::string get_user_for_service(const std::string &server, const std::string &service)
{
auto services_info = get_server_services_info(server);
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() && SIvalid(*it))
return it->user;
return "";
}
sSSHInfo ServerConfig::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(), it->user, get_SSH_PORT(), get_server_name(), it->dir);
}
bool ServerConfig::hasRootUser() const
{
auto it = std::find_if(mUsers.begin(), mUsers.end(),[](const UserConfig &u)
{ return u.user == "root"; });
return it != mUsers.end();
}
bool ServerConfig::hasDocker() const
{
return get_variable("HAS_DOCKER") == "true";
}
bool ServerConfig::hasRootDocker() const
{
return get_variable("DOCKER_ROOTLESS") == "false";
}
bool ServerConfig::hasUser(const std::string &user) const
{
auto it = std::find_if(mUsers.begin(), mUsers.end(),
[&user](const UserConfig &u)
{ return u.user == user; });
return it != mUsers.end();
}
bool ServerConfig::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(user), scommand, cMode::Silent);
}
bool ServerConfig::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(user), scommand, cMode::Silent);
}
bool ServerConfig::check_remote_items_exist(const std::vector<std::string> &file_paths, std::string user) const
{
// convert file_paths to a single string, separated by spaces
std::string file_paths_str;
std::string file_names_str;
for (const auto &file_path : file_paths)
{
file_paths_str += quote(file_path) + " ";
file_names_str += std::filesystem::path(file_path).filename().string() + " ";
}
// 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", {});
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;
}
return true;
}
bool ServerConfig::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();
std::string target_dir = path.filename().string();
if (parent_path.empty())
parent_path = "/";
if (target_dir.empty())
return false;
if (!silent)
std::cout << "Removing remote directory " << target_dir << " in " << parent_path << " on " << mServerName << std::endl;
std::string remote_cmd =
"docker run --rm -v " + quote(parent_path.string()) + ":/parent " +
" alpine rm -rf \"/parent/" + target_dir + "\"";
// if (!silent)
// std::cout << "Running command: " << remote_cmd << std::endl;
sCommand scommand("", remote_cmd, {});
cMode mode = (silent ? cMode::Silent : cMode::Defaults);
sSSHInfo sshinfo = get_SSH_INFO(user);
return execute_ssh_command(sshinfo, scommand, mode);
}
bool ServerConfig::run_remote_template_command(
const std::string &service_name,
const std::string &command,
std::vector<std::string> args,
bool silent,
std::map<std::string, std::string> extra_env_vars) const
{
std::string user = get_user_for_service(service_name);
auto scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
for (const auto &[key, value] : extra_env_vars)
scommand->add_env_var(key, value);
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(user), scommand.value(), mode);
}
bool ServerConfig::run_remote_template_command_and_capture_output(
const std::string &service_name,
const std::string &command,
std::vector<std::string> args,
std::string &output,
bool silent,
std::map<std::string, std::string> extra_env_vars) const
{
std::string user = get_user_for_service(service_name);
auto scommand = construct_standard_template_run_cmd(service_name, command, args, false);
if (!scommand.has_value())
return false;
// add the extra env vars to the command
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);
}
std::string ServerConfig::get_variable(const std::string &name) const
{
auto it = mVariables.find(name);
if (it == mVariables.end())
{
return "";
}
return it->second;
}
std::optional<sCommand> ServerConfig::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const
{
if (command.empty())
return std::nullopt;
std::string user = get_user_for_service(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<std::string, std::string> 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)));
}
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;
}
std::vector<ServerConfig> get_configured_servers()
{
std::vector<ServerConfig> servers;
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths(); std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty()) if (lsdp.empty())
return servers; return servers;
for (auto servers_dir : lsdp) { for (auto servers_dir : lsdp)
if (!servers_dir.empty() && std::filesystem::exists(servers_dir)) { {
for (const auto& entry : std::filesystem::directory_iterator(servers_dir)) { if (!servers_dir.empty() && std::filesystem::exists(servers_dir))
if (std::filesystem::is_directory(entry)) { {
for (const auto &entry : std::filesystem::directory_iterator(servers_dir))
{
if (std::filesystem::is_directory(entry))
{
std::string server_name = entry.path().filename().string(); std::string server_name = entry.path().filename().string();
if (server_name.empty() || server_name[0]=='.' || server_name[0]=='_') if (server_name.empty() || server_name[0] == '.' || server_name[0] == '_')
continue; continue;
server_env_manager env(server_name); ServerConfig env(server_name);
if (!env.is_valid()) { if (!env.is_valid())
{
std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl; std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl;
continue; continue;
} }
servers.push_back({ servers.push_back(env);
server_name,
env.get_SSH_HOST(),
env.get_SSH_USER(),
env.get_SSH_PORT()
});
} }
} }
} }
} }
return servers; return servers;
}
ServerInfo get_server_info(const std::string &server_name)
{
std::vector<std::string> lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty())
return ServerInfo();
for (auto &config_dir : lsdp) {
std::string server_dir = config_dir + "/" + server_name;
if (std::filesystem::exists(server_dir)) {
server_env_manager env(server_name);
if (!env.is_valid()) {
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();
}
bool create_server(const std::string &server_name)
bool create_server(const std::string &server_name) {
{
// 1. check if server name already exists // 1. check if server name already exists
std::string server_existing_dir = localpath::server(server_name); std::string server_existing_dir = localpath::server(server_name);
if (!server_existing_dir.empty()) { if (!server_existing_dir.empty())
std::cerr << "Error: Server name already exists: " << server_name << std::endl; {
std::cerr << "Current server path: " << server_existing_dir << std::endl; error << "Error: Server name already exists: " << server_name << std::endl;
info << "Current server path: " << server_existing_dir << std::endl;
return false; return false;
} }
// 2. create a new directory in the user config directory // 2. create a new directory in the user config directory
auto lsdp = gConfig().get_local_server_definition_paths(); auto lsdp = gConfig().get_local_server_definition_paths();
if (lsdp.empty() || lsdp[0].empty()) { if (lsdp.empty() || lsdp[0].empty())
std::cerr << "Error: Local server definition path not found" << std::endl; {
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl; error << "Error: Local server definition path not found" << std::endl;
info << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false; return false;
} }
std::string server_dir = lsdp[0] + "/" + server_name; std::string server_dir = lsdp[0] + "/" + server_name;
@ -93,33 +406,48 @@ bool create_server(const std::string &server_name)
// 3. create a template server.env file in the server directory // 3. create a template server.env file in the server directory
std::string user = getenv("USER"); std::string user = getenv("USER");
std::string server_env_path = server_dir + "/server.env"; std::string server_env_path = server_dir + "/" + filenames::server_json;
std::ofstream server_env_file(server_env_path); std::ofstream server_env_file(server_env_path);
server_env_file << "SSH_HOST=" << server_name << std::endl; server_env_file << "{" << std::endl;
server_env_file << "SSH_USER=" << user << std::endl; server_env_file << " \"SSH_HOST\": \"" << server_name << "\"," << std::endl;
server_env_file << "SSH_PORT=" << 22 << std::endl; server_env_file << " \"SSH_PORT\": " << 22 << "," << std::endl;
server_env_file << std::endl; server_env_file << " \"SSH_USERS\": [" << std::endl;
server_env_file << "DROPSHELL_DIR=/home/"+user+"/.dropshell" << std::endl; server_env_file << " {" << std::endl;
server_env_file << " \"USER\": \"" << user << "\"," << std::endl;
server_env_file << " \"DIR\": \"" << "/home/" + user << "/.dropshell\"" << std::endl;
server_env_file << " }" << std::endl;
server_env_file << " ]" << std::endl;
server_env_file << "}" << std::endl;
server_env_file.close(); server_env_file.close();
std::cout << "Server created successfully: " << server_name << std::endl; std::cout << "Server created successfully: " << server_name << std::endl;
std::cout << "Please complete the installation:" <<std::endl; std::cout << "Please complete the installation:" << std::endl;
std::cout << "1) edit the server configuration: dropshell edit " << server_name << std::endl; std::cout << "1) edit the server configuration: dropshell edit " << server_name << std::endl;
std::cout << "2) test ssh is working: dropshell ssh " << server_name << std::endl; std::cout << "2) install the server: dropshell install " << server_name << std::endl;
std::cout << "3) install the server: dropshell install " << server_name << std::endl;
std::cout << std::endl; std::cout << std::endl;
return true; return true;
}
void get_all_used_commands(std::set<std::string> &commands)
{
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto& server : servers)
{
auto services = dropshell::get_server_services_info(server.name);
for (const auto& service : services)
commands.merge(dropshell::get_used_commands(server.name, service.service_name));
} }
}
void get_all_used_commands(std::set<std::string> &commands)
{
std::vector<ServerConfig> servers = get_configured_servers();
for (const auto &server : servers)
{
auto services = get_server_services_info(server.get_server_name());
for (const auto &service : services)
commands.merge(get_used_commands(server.get_server_name(), service.service_name));
}
}
bool server_exists(const std::string &server_name)
{
std::string server_existing_dir = localpath::server(server_name);
if (server_existing_dir.empty())
return false;
if (std::filesystem::exists(server_existing_dir));
return true;
return false;
}
} // namespace dropshell } // namespace dropshell

View File

@ -1,28 +1,104 @@
#ifndef SERVERS_HPP // server_env.hpp
#define SERVERS_HPP //
// read the server.env file and provide a class to access the variables
#ifndef __SERVER_ENV_HPP
#define __SERVER_ENV_HPP
#include <string> #include <string>
#include <set> #include <map>
#include <memory>
#include <vector> #include <vector>
#include <set>
#include <optional>
namespace dropshell { #include "utils/execute.hpp"
// Server information structure namespace dropshell
struct ServerInfo { {
std::string name;
std::string ssh_host; struct UserConfig
std::string ssh_user; {
std::string ssh_port; std::string user;
std::string dir;
}; };
std::vector<ServerInfo> get_configured_servers(); // ------------------------------------------------------------------------------------------------
ServerInfo get_server_info(const std::string& server_name); // 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_UNPRIVILEGED_USER
// SSH_PORT
// the following replacements are made in the values:
// ${USER} -> the username of the user running dropshell
class ServerConfig
{
public:
ServerConfig(const std::string &server_name);
bool create_server(const std::string& server_name); bool is_valid() const { return mValid; }
// trivial getters.
const std::map<std::string, std::string> &get_variables() const { return mVariables; }
std::string get_variable(const std::string &name) const;
// ------------------------------------------------------------------------------------------------
// getters
// ------------------------------------------------------------------------------------------------
std::string get_SSH_HOST() const;
std::string get_SSH_PORT() const;
std::vector<UserConfig> get_users() const;
std::string get_user_dir(const std::string &user) const;
std::string get_server_name() const;
std::string get_user_for_service(const std::string &service) const;
sSSHInfo get_SSH_INFO(std::string user) const;
// server capabilities
bool hasRootUser() const;
bool hasDocker() const;
bool hasRootDocker() const;
// helper functions
bool hasUser(const std::string &user) const;
public:
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<std::string> &file_paths, std::string user) 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<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const;
bool run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command,
std::vector<std::string> args, std::string &output, bool silent, std::map<std::string, std::string> extra_env_vars) const;
private:
std::optional<sCommand> construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, const std::vector<std::string> args, const bool silent) const;
private:
std::string mServerName;
std::map<std::string, std::string> mVariables;
std::vector<UserConfig> mUsers;
bool mValid;
}; // class ServerConfig
std::vector<ServerConfig> get_configured_servers();
std::string get_user_for_service(const std::string &server, const std::string &service);
bool create_server(const std::string &server_name);
void get_all_used_commands(std::set<std::string> &commands); void get_all_used_commands(std::set<std::string> &commands);
bool server_exists(const std::string &server_name);
} // namespace dropshell } // namespace dropshell
#endif // SERVERS_HPP #endif // __SERVER_ENV_HPP

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,6 @@
#include "templates.hpp" #include "templates.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "server_env_manager.hpp"
#include "servers.hpp"
#include "assert.hpp" #include "assert.hpp"
#include <iostream> #include <iostream>
@ -13,33 +11,42 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace dropshell { namespace dropshell
{
bool SIvalid(const LocalServiceInfo& service_info) { #pragma message("TODO : Smart test that the service is fully valid.")
bool SIvalid(const LocalServiceInfo &service_info)
{
return !service_info.service_name.empty() && return !service_info.service_name.empty() &&
!service_info.template_name.empty() && !service_info.template_name.empty() &&
!service_info.local_service_path.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<LocalServiceInfo> get_server_services_info(const std::string& server_name) { std::vector<LocalServiceInfo> get_server_services_info(const std::string &server_name)
{
std::vector<LocalServiceInfo> services; std::vector<LocalServiceInfo> services;
if (server_name.empty()) if (server_name.empty())
return services; return services;
std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths(); std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
if (local_server_definition_paths.empty()) { if (local_server_definition_paths.empty())
{
std::cerr << "Error: No local server definition paths found" << std::endl; std::cerr << "Error: No local server definition paths found" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl; std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return services; return services;
} }
for (const auto& server_definition_path : local_server_definition_paths) { for (const auto &server_definition_path : local_server_definition_paths)
{
fs::path serverpath = server_definition_path + "/" + server_name; fs::path serverpath = server_definition_path + "/" + server_name;
if (fs::exists(serverpath)) // service is on that server... if (fs::exists(serverpath)) // service is on that server...
for (const auto& entry : fs::directory_iterator(serverpath)) { for (const auto &entry : fs::directory_iterator(serverpath))
if (fs::is_directory(entry)) { {
if (fs::is_directory(entry))
{
std::string dirname = entry.path().filename().string(); std::string dirname = entry.path().filename().string();
if (dirname.empty() || dirname[0] == '.' || dirname[0] == '_') if (dirname.empty() || dirname[0] == '.' || dirname[0] == '_')
continue; continue;
@ -47,17 +54,27 @@ std::vector<LocalServiceInfo> get_server_services_info(const std::string& server
if (!service.local_service_path.empty()) if (!service.local_service_path.empty())
services.push_back(service); services.push_back(service);
else else
std::cerr << "Warning: Failed to get service info for " << dirname << " on server " << server_name << std::endl; warning << "Failed to get service info for " << dirname << " on server " << server_name << std::endl;
} }
} // end of for } // end of for
} }
return services; return services;
} }
bool get_bool_variable(const std::map<std::string, std::string> &variables, const std::string &variable_name)
{
auto it = variables.find(variable_name);
if (it == variables.end())
{
error << "Variable " << variable_name << " not found in the service " << filenames::template_info_env << std::endl;
return false;
}
return it->second == "true";
}
LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name) LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name)
{ {
LocalServiceInfo service; LocalServiceInfo service;
if (server_name.empty() || service_name.empty()) if (server_name.empty() || service_name.empty())
@ -81,50 +98,82 @@ LocalServiceInfo get_service_info(const std::string &server_name, const std::str
if (!get_all_service_env_vars(server_name, service_name, variables)) if (!get_all_service_env_vars(server_name, service_name, variables))
return LocalServiceInfo(); return LocalServiceInfo();
// confirm TEMPLATE is defined. { // confirm TEMPLATE is defined.
auto it = variables.find("TEMPLATE"); auto it = variables.find("TEMPLATE");
if (it == variables.end()) { if (it == variables.end())
std::cerr << "Error: TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl; {
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
return LocalServiceInfo(); return LocalServiceInfo();
} }
service.template_name = it->second; service.template_name = it->second;
}
template_info tinfo = gTemplateManager().get_template_info(service.template_name); template_info tinfo = gTemplateManager().get_template_info(service.template_name);
if (!tinfo.is_set()) { if (!tinfo.is_set())
std::cerr << "Error: Template '" << service.template_name << "' not found" << std::endl; {
error << "Template specified '" << service.template_name << "' could not be found" << std::endl;
return LocalServiceInfo(); return LocalServiceInfo();
} }
// find the template path // find the template path
service.local_template_path = tinfo.local_template_path(); service.local_template_path = tinfo.local_template_path();
return service; { // set the user.
} auto it = variables.find("SSH_USER");
if (it == variables.end())
{
error << "SSH_USER variable not defined in service " << service_name << " on server " << server_name << std::endl;
return LocalServiceInfo();
}
service.user = it->second;
}
std::set<std::string> get_used_commands(const std::string &server_name, const std::string &service_name) // set the host root and docker requirements.
{ service.requires_host_root = get_bool_variable(variables, "REQUIRES_HOST_ROOT");
service.requires_docker = get_bool_variable(variables, "REQUIRES_DOCKER");
service.requires_docker_root = get_bool_variable(variables, "REQUIRES_DOCKER_ROOT");
{ // 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;
else
{
uint64_t service_template_hash = std::stoull(it->second);
service.service_template_hash_match = (service_template_hash == tinfo.hash());
//debug << "Service template hash: " << service_template_hash << " == " << tinfo.hash() << std::endl;
}
}
return service;
}
std::set<std::string> get_used_commands(const std::string &server_name, const std::string &service_name)
{
std::set<std::string> commands; std::set<std::string> commands;
if (server_name.empty() || service_name.empty()) if (server_name.empty() || service_name.empty())
return commands; return commands;
auto service_info = get_service_info(server_name, service_name); auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty()) { if (service_info.local_template_path.empty())
{
std::cerr << "Error: Service not found: " << service_name << std::endl; std::cerr << "Error: Service not found: " << service_name << std::endl;
return commands; return commands;
} }
// iterate over all files in the template path, and add the command name to the set. // iterate over all files in the template path, and add the command name to the set.
// commands are .sh files that don't begin with _ // commands are .sh files that don't begin with _
for (const auto& entry : fs::directory_iterator(service_info.local_template_path)) { for (const auto &entry : fs::directory_iterator(service_info.local_template_path))
{
if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0)) if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0))
commands.insert(entry.path().stem().string()); commands.insert(entry.path().stem().string());
} }
return commands; return commands;
} }
std::set<std::string> list_backups(const std::string &server_name, const std::string &service_name) std::set<std::string> list_backups(const std::string &server_name, const std::string &service_name)
{ {
std::set<std::string> backups; std::set<std::string> backups;
if (server_name.empty() || service_name.empty()) if (server_name.empty() || service_name.empty())
@ -132,17 +181,20 @@ std::set<std::string> list_backups(const std::string &server_name, const std::st
// need to find the template for the service. // need to find the template for the service.
auto service_info = get_service_info(server_name, service_name); auto service_info = get_service_info(server_name, service_name);
if (service_info.local_template_path.empty()) { if (service_info.local_template_path.empty())
{
std::cerr << "Error: Service not found: " << service_name << std::endl; std::cerr << "Error: Service not found: " << service_name << std::endl;
return backups; return backups;
} }
std::string backups_dir = gConfig().get_local_backup_path(); std::string backups_dir = localpath::backups();
if (backups_dir.empty()) if (backups_dir.empty())
return backups; return backups;
if (fs::exists(backups_dir)) { if (fs::exists(backups_dir))
for (const auto& entry : fs::directory_iterator(backups_dir)) { {
for (const auto &entry : fs::directory_iterator(backups_dir))
{
if (fs::is_regular_file(entry) && entry.path().extension() == ".tgz") if (fs::is_regular_file(entry) && entry.path().extension() == ".tgz")
if (entry.path().filename().string().find(service_info.template_name) != std::string::npos) if (entry.path().filename().string().find(service_info.template_name) != std::string::npos)
{ {
@ -151,12 +203,10 @@ std::set<std::string> list_backups(const std::string &server_name, const std::st
} }
} }
return backups; return backups;
} }
bool get_all_service_env_vars(const std::string &server_name, const std::string &service_name, std::map<std::string, std::string> &all_env_vars)
{
bool get_all_service_env_vars(const std::string &server_name, const std::string &service_name, std::map<std::string, std::string> & all_env_vars)
{
all_env_vars.clear(); all_env_vars.clear();
if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name))) if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name)))
@ -165,22 +215,11 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
return false; return false;
} }
ServerInfo server_info = get_server_info(server_name);
if (server_info.ssh_host.empty())
std::cerr << "Error: Server " << server_name << " not found - ssh_host empty, so HOST_NAME not set" << std::endl;
// 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["SERVER"] = server_name;
all_env_vars["SERVICE"] = service_name;
all_env_vars["AGENT_PATH"] = remotepath::agent(server_name);
all_env_vars["HOST_NAME"] = server_info.ssh_host;
all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.
// Lambda function to load environment variables from a file // Lambda function to load environment variables from a file
auto load_env_file = [&all_env_vars](const std::string& file) { auto load_env_file = [&all_env_vars](const std::string &file)
if (!file.empty() && std::filesystem::exists(file)) { {
if (!file.empty() && std::filesystem::exists(file))
{
std::map<std::string, std::string> env_vars; std::map<std::string, std::string> env_vars;
envmanager env_manager(file); envmanager env_manager(file);
env_manager.load(); env_manager.load();
@ -188,37 +227,49 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
all_env_vars.merge(env_vars); all_env_vars.merge(env_vars);
} }
else else
std::cout << "Warning: Expected environment file not found: " << file << std::endl; warning << "Expected environment file not found: " << file << std::endl;
}; };
// Load environment files // Load environment files
load_env_file(localfile::service_env(server_name, service_name)); load_env_file(localfile::service_env(server_name, service_name));
load_env_file(localfile::template_info_env(server_name, service_name)); load_env_file(localfile::template_info_env(server_name, service_name));
std::string user = all_env_vars["SSH_USER"];
if (user.empty())
{
error << "SSH_USER variable not defined in service " << service_name << " on server " << server_name << std::endl;
info << "This variable definition is always required, and usually set in the "<<filenames::service_env << " file." << std::endl;
info << "Please check " << localfile::service_env(server_name, service_name) << std::endl;
return false;
}
// add in some handy variables.
// if we change these, we also need to update agent/_allservicesstatus.sh
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(server_name, user).agent();
all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.
// determine template name. // determine template name.
auto it = all_env_vars.find("TEMPLATE"); auto it = all_env_vars.find("TEMPLATE");
if (it == all_env_vars.end()) { if (it == all_env_vars.end())
std::cerr << std::endl << std::endl; {
std::cerr << "Error: TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl; error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
std::cerr << "The TEMPLATE variable is required to determine the template name." << std::endl; info << "The TEMPLATE variable is required to determine the template name." << std::endl;
std::cerr << "Please check the service.env file and the .template_info.env file in:" << std::endl; info << "Please check the " << filenames::service_env << " file and the "<< filenames::template_info_env << " file in:" << std::endl;
std::cerr << " " << localpath::service(server_name, service_name) << std::endl << std::endl; info << " " << localpath::service(server_name, service_name) << std::endl
<< std::endl;
return false; return false;
} }
template_info tinfo = gTemplateManager().get_template_info(it->second); template_info tinfo = gTemplateManager().get_template_info(it->second);
if (!tinfo.is_set()) { if (!tinfo.is_set())
{
std::cerr << "Error: Template '" << it->second << "' not found" << std::endl; std::cerr << "Error: Template '" << it->second << "' not found" << std::endl;
return false; return false;
} }
std::string default_env_file = tinfo.local_template_path()/"_default.env"; return true;
if (!fs::exists(default_env_file)) {
std::cerr << "Error: Template default env file '" << default_env_file << "' not found" << std::endl;
return false;
} }
load_env_file(default_env_file);
return true;
}
} // namespace dropshell } // namespace dropshell

View File

@ -13,6 +13,11 @@ namespace dropshell {
std::string template_name; std::string template_name;
std::string local_service_path; std::string local_service_path;
std::string local_template_path; std::string local_template_path;
std::string user;
bool requires_host_root;
bool requires_docker;
bool requires_docker_root;
bool service_template_hash_match;
}; };
bool SIvalid(const LocalServiceInfo& service_info); bool SIvalid(const LocalServiceInfo& service_info);
@ -27,6 +32,7 @@ namespace dropshell {
// list all backups for a given service (across all servers) // list all backups for a given service (across all servers)
std::set<std::string> list_backups(const std::string& server_name, const std::string& service_name); std::set<std::string> list_backups(const std::string& server_name, const std::string& service_name);
} // namespace dropshell } // namespace dropshell
#endif #endif

View File

@ -13,6 +13,7 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "templates.hpp" #include "templates.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/hash.hpp"
namespace dropshell { namespace dropshell {
@ -185,7 +186,7 @@
return false; return false;
} }
auto local_template_paths = gConfig().get_template_local_paths(); auto local_template_paths = gConfig().get_local_template_paths();
if (local_template_paths.empty()) { if (local_template_paths.empty()) {
std::cerr << "Error: No local template paths found" << std::endl; std::cerr << "Error: No local template paths found" << std::endl;
std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl; std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl;
@ -219,9 +220,9 @@
// modify the TEMPLATE=example line in the .template_info.env file to TEMPLATE=<template_name> // modify the TEMPLATE=example line in the .template_info.env file to TEMPLATE=<template_name>
std::string search_string = "TEMPLATE="; std::string search_string = "TEMPLATE=";
std::string replacement_line = "TEMPLATE=" + template_name; std::string replacement_line = "TEMPLATE=" + template_name;
std::string service_env_path = new_template_path + "/config/.template_info.env"; std::string service_env_path = new_template_path + "/config/" + filenames::template_info_env;
if (!replace_line_in_file(service_env_path, search_string, replacement_line)) { if (!replace_line_in_file(service_env_path, search_string, replacement_line)) {
std::cerr << "Error: Failed to replace TEMPLATE= line in the .template_info.env file" << std::endl; std::cerr << "Error: Failed to replace TEMPLATE= line in the " << filenames::template_info_env <<" file" << std::endl;
return false; return false;
} }
@ -252,7 +253,7 @@
ASSERT(mSources.empty(), "Template manager already loaded (sources are not empty)."); ASSERT(mSources.empty(), "Template manager already loaded (sources are not empty).");
ASSERT(gConfig().is_config_set(), "Config not set."); ASSERT(gConfig().is_config_set(), "Config not set.");
ASSERT(!mLoaded, "Template manager already loaded."); ASSERT(!mLoaded, "Template manager already loaded.");
auto local_template_paths = gConfig().get_template_local_paths(); auto local_template_paths = gConfig().get_local_template_paths();
if (local_template_paths.empty()) if (local_template_paths.empty())
return; return;
for (const auto& path : local_template_paths) for (const auto& path : local_template_paths)
@ -305,9 +306,8 @@
std::string template_name = std::filesystem::path(template_path).filename().string(); std::string template_name = std::filesystem::path(template_path).filename().string();
std::vector<std::string> required_files = { std::vector<std::string> required_files = {
"config/service.env", "config/" + filenames::service_env,
"config/.template_info.env", "config/" + filenames::template_info_env,
"_default.env",
"install.sh", "install.sh",
"uninstall.sh" "uninstall.sh"
}; };
@ -331,8 +331,8 @@
// check TEMPLATE= line. // check TEMPLATE= line.
std::map<std::string, std::string> all_env_vars; std::map<std::string, std::string> all_env_vars;
std::vector<std::string> env_files = { std::vector<std::string> env_files = {
"config/service.env", "config/" + filenames::service_env,
"config/.template_info.env" "config/" + filenames::template_info_env
}; };
for (const auto& file : env_files) { for (const auto& file : env_files) {
{ // load service.env from the service on this machine. { // load service.env from the service on this machine.
@ -376,8 +376,16 @@
mLocationID(location_id), mLocationID(location_id),
mTemplateLocalPath(local_template_path), mTemplateLocalPath(local_template_path),
mTemplateValid(template_manager::test_template(local_template_path.string())), mTemplateValid(template_manager::test_template(local_template_path.string())),
mIsSet(!template_name.empty() && !location_id.empty() && !local_template_path.empty()) mIsSet(!template_name.empty() && !location_id.empty() && !local_template_path.empty()),\
mHash(0)
{ {
if (!std::filesystem::exists(local_template_path))
{
error << "Template path does not exist: " << local_template_path << std::endl;
return;
}
mHash = hash_directory_recursive(local_template_path);
} }
} // namespace dropshell } // namespace dropshell

View File

@ -4,7 +4,9 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include "utils/json.hpp" #define JSON_INLINE_ALL
#include "json.hpp"
namespace dropshell { namespace dropshell {
@ -19,17 +21,20 @@ class template_info {
template_info() : mIsSet(false) {} template_info() : mIsSet(false) {}
template_info(const std::string& template_name, const std::string& location_id, const std::filesystem::path& local_template_path); template_info(const std::string& template_name, const std::string& location_id, const std::filesystem::path& local_template_path);
virtual ~template_info() {} virtual ~template_info() {}
bool is_set() { return mIsSet; } bool is_set() const { return mIsSet; }
std::string name() { return mTemplateName; } std::string name() const { return mTemplateName; }
std::string locationID() { return mLocationID; } std::string locationID() const { return mLocationID; }
std::filesystem::path local_template_path() { return mTemplateLocalPath; } std::filesystem::path local_template_path() const { return mTemplateLocalPath; }
bool template_valid() { return mTemplateValid; } bool template_valid() const { return mTemplateValid; }
uint64_t hash() const { return mHash; }
private: private:
std::string mTemplateName; std::string mTemplateName;
std::string mLocationID; std::string mLocationID;
std::filesystem::path mTemplateLocalPath; // source or cache. std::filesystem::path mTemplateLocalPath; // source or cache.
bool mTemplateValid; bool mTemplateValid;
bool mIsSet; bool mIsSet;
uint64_t mHash;
}; };
class template_source_interface { class template_source_interface {

View File

@ -9,4 +9,5 @@
std::exit(1); \ std::exit(1); \
} }
#endif // ASSERT_HPP #endif // ASSERT_HPP

View File

@ -1,10 +1,13 @@
#include "directories.hpp" #include "directories.hpp"
#include "config.hpp" #include "config.hpp"
#include "server_env_manager.hpp" #include "servers.hpp"
#include "output.hpp"
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace dropshell { namespace dropshell {
@ -16,7 +19,7 @@ namespace localfile {
// Try ~/.config/dropshell/dropshell.json // Try ~/.config/dropshell/dropshell.json
std::string homedir = localpath::current_user_home(); std::string homedir = localpath::current_user_home();
if (!homedir.empty()) { if (!homedir.empty()) {
fs::path user_path = fs::path(homedir) / ".config" / "dropshell" / "dropshell.json"; fs::path user_path = fs::path(homedir) / ".config" / "dropshell" / filenames::dropshell_json;
return user_path.string(); return user_path.string();
} }
return std::string(); return std::string();
@ -24,18 +27,28 @@ namespace localfile {
std::string server_json(const std::string &server_name) { std::string server_json(const std::string &server_name) {
std::string serverpath = localpath::server(server_name); std::string serverpath = localpath::server(server_name);
return (serverpath.empty() ? "" : (fs::path(serverpath) / "server.json").string()); return (serverpath.empty() ? "" : (fs::path(serverpath) / filenames::server_json).string());
} }
std::string service_env(const std::string &server_name, const std::string &service_name) { std::string service_env(const std::string &server_name, const std::string &service_name) {
std::string servicepath = localpath::service(server_name, service_name); std::string servicepath = localpath::service(server_name, service_name);
return (servicepath.empty() ? "" : (fs::path(servicepath) / "service.env").string()); return (servicepath.empty() ? "" : (fs::path(servicepath) / filenames::service_env).string());
} }
std::string template_info_env(const std::string &server_name, const std::string &service_name) std::string template_info_env(const std::string &server_name, const std::string &service_name)
{ {
std::string servicepath = localpath::service(server_name, service_name); std::string servicepath = localpath::service(server_name, service_name);
return (servicepath.empty() ? "" : (fs::path(servicepath) / ".template_info.env").string()); return (servicepath.empty() ? "" : (fs::path(servicepath) / filenames::template_info_env).string());
}
std::string template_example()
{
return localpath::agent_local() + "/template_example";
}
std::string bb64()
{
return localpath::agent_local() + "/bb64";
} }
} // namespace localfile } // namespace localfile
@ -59,16 +72,17 @@ namespace localpath {
std::string remote_versions(const std::string &server_name, const std::string &service_name) std::string remote_versions(const std::string &server_name, const std::string &service_name)
{ {
std::string template_cache_path = gConfig().get_local_template_cache_path(); std::string template_cache_path = localpath::template_cache();
return ((template_cache_path.empty() || service_name.empty()) ? "" : return ((template_cache_path.empty() || service_name.empty()) ? "" :
(template_cache_path+"/remote_versions/"+service_name+".json")); (template_cache_path+"/remote_versions/"+service_name+".json"));
} }
std::string agent(){ std::string agent_local()
return current_user_home() + "/.local/dropshell_agent";
}
std::string files_for_remote_agent()
{ {
return agent() + "/files_for_remote_agent"; return current_user_home()+"/.local/dropshell_agent/agent-local";
}
std::string agent_remote()
{
return current_user_home() + "/.local/dropshell_agent/agent-remote";
} }
std::string current_user_home() std::string current_user_home()
{ {
@ -78,9 +92,53 @@ namespace localpath {
std::filesystem::path homedir_path(homedir); std::filesystem::path homedir_path(homedir);
return fs::canonical(homedir_path).string(); return fs::canonical(homedir_path).string();
} }
std::cerr << "Warning: Couldn't determine user directory" << std::endl; warning << "Couldn't determine user directory" << std::endl;
return std::string(); return std::string();
} }
std::string dropshell_files()
{
return current_user_home() + "/.local/dropshell_files";
return std::string();
}
std::string backups()
{
return dropshell_files() + "/backups";
}
std::string temp_files()
{
return dropshell_files() + "/temp_files";
}
std::string template_cache()
{
return dropshell_files() + "template_cache";
}
bool create_directories()
{
std::vector<std::filesystem::path> paths = {
dropshell_files(),
agent_local(),
agent_remote(),
template_cache(),
backups(),
temp_files()
};
for (auto &p : gConfig().get_local_server_definition_paths())
paths.push_back(p);
for (auto &p : paths)
if (!p.empty() && !std::filesystem::exists(p))
{
info << "Creating directory: " << p << std::endl;
std::filesystem::create_directories(p);
}
return false;
}
} // namespace localpath } // namespace localpath
//------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------
@ -96,78 +154,70 @@ namespace localpath {
// |-- config // |-- config
// |-- service.env (actual service config) // |-- service.env (actual service config)
// |-- template // |-- template
// |-- _default.env
// |-- (script files) // |-- (script files)
// |-- config // |-- config
// |-- service.env (default service config) // |-- service.env (default service config)
// |-- (other config files for specific server&service) // |-- (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 remotefile::service_env(const std::string &service_name) const
std::string service_env(const std::string &server_name, const std::string &service_name)
{ {
return remotepath::service_config(server_name, service_name) + "/service.env"; return remotepath(mServer_name,mUser).service_config(service_name) + "/" + filenames::service_env;
} }
}
namespace remotepath { remotepath::remotepath(const std::string &server_name, const std::string &user) : mServer_name(server_name), mUser(user) {}
std::string DROPSHELL_DIR(const std::string &server_name)
std::string remotepath::DROPSHELL_DIR() const
{ {
return server_env_manager(server_name).get_DROPSHELL_DIR(); return ServerConfig(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")); 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)); 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")); 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")); 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")); 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")); 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")); 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
// ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------
// Utility functions // Utility functions

View File

@ -14,9 +14,26 @@ namespace dropshell {
// ~/.config/dropshell/dropshell.json // ~/.config/dropshell/dropshell.json
// ~/.local/dropshell_agent // ~/.local/dropshell_agent
// |-- agent-local
// |-- agent-install.sh
// |-- bb64 (only used locally, as it's for the local machine's architecture!) // |-- bb64 (only used locally, as it's for the local machine's architecture!)
// |-- files_for_remote_agent // |-- template_example
// |-- (other agent files, including _allservicesstatus.sh) // |-- agent-remote
// |-- (remote agent files, including _allservicesstatus.sh)
// ~/.local/dropshell_files
// |-- backups
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// |-- temp_files
// |-- template_cache
// |-- templates
// | |-- <template_name>.json
// | |-- <template_name>
// | |-- (...script files...)
// | |-- config
// | |-- service.env
// | |-- .template_info.env
// | |-- (...other service config files...)
// server_definition_path // server_definition_path
// |-- <server_name> // |-- <server_name>
@ -27,23 +44,13 @@ namespace dropshell {
// |-- .template_info.env // |-- .template_info.env
// |-- (...other config files for specific server&service...) // |-- (...other config files for specific server&service...)
// backup path namespace filenames {
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz static const std::string template_info_env = ".template_info.env";
static const std::string service_env = "service.env";
// temp files path static const std::string readme = "README.txt";
static const std::string server_json = "server.json";
// template cache path static const std::string dropshell_json = "dropshell.json";
// |-- templates } // namespace filenames.
// | |-- <template_name>.json
// | |-- <template_name>
// | |-- (...script files...)
// | |-- _default.env
// | |-- config
// | |-- service.env
// | |-- .template_info.env
// | |-- (...other service config files...)
// |-- remote_versions
// | |-- server_name-service_name.json
namespace localfile { namespace localfile {
// ~/.config/dropshell/dropshell.json // ~/.config/dropshell/dropshell.json
@ -51,6 +58,8 @@ namespace dropshell {
std::string server_json(const std::string &server_name); 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 service_env(const std::string &server_name, const std::string &service_name);
std::string template_info_env(const std::string &server_name, const std::string &service_name); std::string template_info_env(const std::string &server_name, const std::string &service_name);
std::string template_example();
std::string bb64();
} // namespace localfile } // namespace localfile
namespace localpath { namespace localpath {
@ -59,9 +68,16 @@ namespace dropshell {
std::string remote_versions(const std::string &server_name, const std::string &service_name); std::string remote_versions(const std::string &server_name, const std::string &service_name);
std::string agent(); std::string agent_local();
std::string files_for_remote_agent(); std::string agent_remote();
std::string current_user_home(); std::string current_user_home();
std::string dropshell_files();
std::string backups();
std::string temp_files();
std::string template_cache();
bool create_directories();
} // namespace local } // namespace local
@ -79,27 +95,36 @@ namespace dropshell {
// |-- service.env (actual service config) // |-- service.env (actual service config)
// |-- .template_info.env // |-- .template_info.env
// |-- template // |-- template
// |-- _default.env
// |-- (script files) // |-- (script files)
// |-- config // |-- config
// |-- service.env (default service config) // |-- service.env (default service config)
// |-- .template_info.env // |-- .template_info.env
// |-- (other config files for specific server&service) // |-- (other config files for specific server&service)
namespace remotefile { class remotefile {
std::string service_env(const std::string &server_name, const std::string &service_name); public:
} // namespace remotefile 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 { class remotepath {
std::string DROPSHELL_DIR(const std::string &server_name); public:
std::string services(const std::string &server_name); remotepath(const std::string &server_name, const std::string &user);
std::string service(const std::string &server_name, const std::string &service_name); std::string DROPSHELL_DIR() const;
std::string service_config(const std::string &server_name, const std::string &service_name); std::string services() const;
std::string service_template(const std::string &server_name, const std::string &service_name); std::string service(const std::string &service_name) const;
std::string backups(const std::string &server_name); std::string service_config(const std::string &service_name) const;
std::string temp_files(const std::string &server_name); std::string service_template(const std::string &service_name) const;
std::string agent(const std::string &server_name); std::string backups() const;
} // namespace remotepath std::string temp_files() const;
std::string agent() const;
private:
std::string mServer_name;
std::string mUser;
};
//------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------
// utility functions // utility functions

View File

@ -22,7 +22,6 @@ namespace dropshell
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute. return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
} }
// ---------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------
// execute_local_command_interactive // execute_local_command_interactive
// ---------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------
@ -30,7 +29,7 @@ namespace dropshell
{ {
if (command.get_command_to_run().empty()) if (command.get_command_to_run().empty())
return false; return false;
std::string full_command = command.construct_cmd(localpath::agent()); // Get the command string std::string full_command = command.construct_cmd(localfile::bb64()); // Get the command string
pid_t pid = fork(); pid_t pid = fork();
@ -85,15 +84,19 @@ namespace dropshell
currentColour_ = startColour_; currentColour_ = startColour_;
} }
colourstream(currentColour_) << chunk; 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; size_t start = 0;
while (start < buffer.size()) { while (start < buffer.size())
{
size_t newline_pos = buffer.find('\n', start); size_t newline_pos = buffer.find('\n', start);
if (newline_pos == std::string::npos) { if (newline_pos == std::string::npos)
if (start < buffer.size()) { {
if (start < buffer.size())
{
print_chunk(buffer.substr(start)); print_chunk(buffer.substr(start));
} }
break; break;
@ -109,26 +112,30 @@ namespace dropshell
sColour currentColour_; sColour currentColour_;
}; };
bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> &env_vars, std::string *output, cMode mode) bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> &env_vars, std::string *output, cMode mode)
{ {
sCommand command(directory_to_run_in, command_to_run, env_vars); sCommand command(directory_to_run_in, command_to_run, env_vars);
if (hasFlag(mode, cMode::Interactive)) 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"); ASSERT(output == nullptr, "Interactive mode and an output string cannot be used together");
return execute_local_command_interactive(command); return execute_local_command_interactive(command);
} }
if (command.get_command_to_run().empty()) if (command.get_command_to_run().empty())
return false; return false;
bool silent = hasFlag(mode, cMode::Silent); 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(localfile::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"); FILE *pipe = popen(full_cmd.c_str(), "r");
if (!pipe) if (!pipe)
@ -157,17 +164,18 @@ namespace dropshell
if (remote_command.get_command_to_run().empty()) if (remote_command.get_command_to_run().empty())
return false; return false;
ASSERT(!(hasFlag(mode, cMode::CaptureOutput) && output == nullptr), "Capture output mode must be used with an output string");
std::stringstream ssh_cmd; std::stringstream ssh_cmd;
ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "") ssh_cmd << "ssh -p " << ssh_info.get_port() << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "")
<< ssh_info.user << "@" << ssh_info.host; << ssh_info.get_user() << "@" << ssh_info.get_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(ssh_info.get_server_ID(), ssh_info.get_user()).agent() + "/bb64";
bool rval = execute_local_command( bool rval = execute_local_command(
"", // directory to run in "", // local directory to run in
ssh_cmd.str() + " " + remote_command.construct_cmd(remote_agent_path), // local command to run ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path), // local command to run
{}, // environment variables {}, // environment variables
output, // output string output, // output string
mode // mode mode // mode
@ -175,12 +183,8 @@ namespace dropshell
if (!rval && !hasFlag(mode, cMode::Silent)) if (!rval && !hasFlag(mode, cMode::Silent))
{ {
std::cerr << std::endl error << "Error: Failed to execute ssh command:" << std::endl;
<< std::endl; debug << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path) << 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;
} }
return rval; return rval;
} }
@ -188,19 +192,19 @@ namespace dropshell
// ---------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------
// makesafecmd // 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()) if (command.empty())
return ""; return "";
std::string encoded = base64_encode(dequote(trim(command))); std::string encoded = base64_encode(dequote(trim(command)));
std::string commandstr = agent_path + "/bb64 " + encoded; std::string commandstr = bb64path + " " + encoded;
return commandstr; return commandstr;
} }
// ---------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------
// construct_cmd // construct_cmd
// ---------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------
std::string sCommand::construct_cmd(std::string agent_path) const std::string sCommand::construct_cmd(std::string bb64path) const
{ {
if (mCmd.empty()) if (mCmd.empty())
return ""; return "";
@ -208,6 +212,8 @@ namespace dropshell
// need to construct to change directory and set environment variables // need to construct to change directory and set environment variables
std::string cmdstr; std::string cmdstr;
if (!bb64path.empty())
{
if (!mDir.empty()) if (!mDir.empty())
cmdstr += "cd " + quote(mDir) + " && "; cmdstr += "cd " + quote(mDir) + " && ";
@ -217,10 +223,29 @@ namespace dropshell
cmdstr += mCmd; cmdstr += mCmd;
if (!agent_path.empty()) cmdstr = makesafecmd(bb64path, cmdstr);
cmdstr = makesafecmd(agent_path, 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; return cmdstr;
} }
bool sSSHInfo::valid() const
{
if (host.empty() || user.empty() || port.empty() || server_ID.empty() || user_dir.empty())
return false;
if (atoi(port.c_str()) == 0)
return false;
return true;
}
} // namespace dropshell } // namespace dropshell

View File

@ -13,7 +13,7 @@ enum class cMode {
Defaults = 0, Defaults = 0,
Interactive = 1, Interactive = 1,
Silent = 2, Silent = 2,
CaptureOutput = 4 NoBB64 = 4
}; };
inline cMode operator&(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) & static_cast<int>(rhs));} inline cMode operator&(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) & static_cast<int>(rhs));}
@ -24,12 +24,25 @@ inline cMode operator|=(cMode & lhs, cMode rhs) {return lhs = lhs | rhs;}
inline bool hasFlag(cMode mode, cMode flag) {return (mode & flag) == flag;} inline bool hasFlag(cMode mode, cMode flag) {return (mode & flag) == flag;}
typedef struct sSSHInfo { class sSSHInfo {
public:
sSSHInfo(std::string host, std::string user, std::string port, std::string server_ID, std::string user_dir) :
host(host), user(user), port(port), server_ID(server_ID), user_dir(user_dir) {}
std::string get_host() const { return host; }
std::string get_user() const { return user; }
std::string get_port() const { return port; }
std::string get_server_ID() const { return server_ID; }
std::string get_user_dir() const { return user_dir; }
bool valid() const;
private:
std::string host; std::string host;
std::string user; std::string user;
std::string port; std::string port;
std::string server_ID; // dropshell name for server. std::string server_ID; // dropshell name for server.
} sSSHInfo; std::string user_dir; // dropshell directory for the user.
};
bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars, std::string * output = nullptr, cMode mode = cMode::Defaults); bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars, std::string * output = nullptr, cMode mode = cMode::Defaults);
bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_command, cMode mode = cMode::Defaults, std::string * output = nullptr); bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_command, cMode mode = cMode::Defaults, std::string * output = nullptr);
@ -52,10 +65,10 @@ class sCommand {
bool empty() const { return mCmd.empty(); } bool empty() const { return mCmd.empty(); }
std::string construct_cmd(std::string agent_path) const; std::string construct_cmd(std::string bb64path) const;
private: 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: private:
std::string mDir; std::string mDir;

View File

@ -42,7 +42,7 @@ namespace dropshell
std::lock_guard<std::mutex> lock(output_mutex); std::lock_guard<std::mutex> lock(output_mutex);
if (c == EOF) if (c == EOF)
return !EOF; return !EOF;
if (at_line_start_ && c != '\n') if (at_line_start_) // && c != '\n')
{ {
dest_ << GREY << tag_ << RESET << ' ' << colour_; dest_ << GREY << tag_ << RESET << ' ' << colour_;
at_line_start_ = false; at_line_start_ = false;

View File

@ -147,6 +147,27 @@ void tableprint::set_title(const std::string title) {
this->title = title; this->title = title;
} }
// gives the columns to sort by, starting at 0.
void tableprint::sort(std::vector<int> sort_columns)
{
// Skip header row and sort remaining rows
if (rows.size() <= 1) return; // Only header or empty table
// Create a custom comparator that compares rows based on the specified columns
auto comparator = [this, &sort_columns](const std::vector<std::string>& a, const std::vector<std::string>& b) {
for (int col : sort_columns) {
if (col >= 0 && col < a.size() && col < b.size()) {
int cmp = a[col].compare(b[col]);
if (cmp != 0) return cmp < 0;
}
}
return false; // Equal rows maintain original order
};
// Sort rows starting from index 1 (after header)
std::sort(rows.begin() + 1, rows.end(), comparator);
}
void tableprint::add_row(const std::vector<std::string>& row) { void tableprint::add_row(const std::vector<std::string>& row) {
std::vector<std::string> trimmed_row; std::vector<std::string> trimmed_row;
for (const auto& cell : row) { for (const auto& cell : row) {

View File

@ -16,6 +16,7 @@ class tableprint {
void add_row(const std::vector<std::string>& row); void add_row(const std::vector<std::string>& row);
void print(); void print();
void set_title(const std::string title); void set_title(const std::string title);
void sort(std::vector<int> sort_columns);
private: private:
std::vector<std::vector<std::string>> rows; std::vector<std::vector<std::string>> rows;
std::string title; std::string title;

View File

@ -7,9 +7,22 @@
#include <filesystem> #include <filesystem>
#include <regex> #include <regex>
#include <random> #include <random>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cctype>
namespace dropshell { namespace dropshell {
std::string magic_string() {
return "-_-";
}
bool has_magic_string(std::string name)
{
return name.find(magic_string()) != std::string::npos;
}
void maketitle(const std::string& title, sColour colour) { void maketitle(const std::string& title, sColour colour) {
colourstream(colour) << std::string(title.length() + 4, '-') << std::endl; colourstream(colour) << std::string(title.length() + 4, '-') << std::endl;
colourstream(colour) << "| " << title << " |" << std::endl; colourstream(colour) << "| " << title << " |" << std::endl;
@ -276,29 +289,6 @@ std::vector<std::string> split(const std::string& str, const std::string& delimi
return tokens; return tokens;
} }
std::string replace_with_environment_variables_like_bash(std::string str) {
// Combined regex pattern for both ${var} and $var formats
std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))");
std::string result = str;
std::smatch match;
while (std::regex_search(result, match, var_pattern)) {
// match[1] will contain capture from ${var} format
// match[2] will contain capture from $var format
std::string var_name = match[1].matched ? match[1].str() : match[2].str();
// Get value from system environment variables
const char* env_value = std::getenv(var_name.c_str());
std::string value = env_value ? env_value : "";
result = result.replace(match.position(), match.length(), value);
}
// dequote the result
return result;
}
std::string random_alphanumeric_string(int length) std::string random_alphanumeric_string(int length)
{ {
static std::mt19937 generator(std::random_device{}()); static std::mt19937 generator(std::random_device{}());
@ -356,5 +346,103 @@ std::string center_align(const std::string & str, int width) {
} }
std::string replace_with_environment_variables_like_bash(std::string str) {
// Combined regex pattern for both ${var} and $var formats
std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))");
std::string result = str;
std::smatch match;
while (std::regex_search(result, match, var_pattern)) {
// match[1] will contain capture from ${var} format
// match[2] will contain capture from $var format
std::string var_name = match[1].matched ? match[1].str() : match[2].str();
// Get value from system environment variables
const char* env_value = std::getenv(var_name.c_str());
std::string value = env_value ? env_value : "";
result = result.replace(match.position(), match.length(), value);
}
// dequote the result
return result;
}
std::string substitute_provided_key_value_pairs(std::string str, const std::map<std::string, std::string> &env_vars)
{
// Combined regex pattern for both ${var} and $var formats
std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))");
std::string result = str;
std::smatch match;
while (std::regex_search(result, match, var_pattern)) {
// match[1] will contain capture from ${var} format
// match[2] will contain capture from $var format
std::string var_name = match[1].matched ? match[1].str() : match[2].str();
// Get value from environment variables map
auto it = env_vars.find(var_name);
std::string value = (it != env_vars.end()) ? it->second : "";
result = result.replace(match.position(), match.length(), value);
}
return result;
}
int get_console_width()
{
struct winsize w;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
return w.ws_col;
}
// Fallback to a reasonable default if we can't get the width
return 80;
}
std::string remove_return(std::string str)
{
str.erase(std::remove(str.begin(), str.end(), '\n'), str.end());
return str;
}
std::string get_line_wrap(std::string &src, int maxchars)
{
if (src.empty())
return "";
if (src.length() <= maxchars)
{
std::string out = src;
src.erase();
return remove_return(out) + '\n';
}
// find last whitespace up to but not more than maxchars
size_t grab_to=maxchars;
size_t lastreturn = src.rfind('\n', maxchars);
size_t lastspace = src.rfind(' ', maxchars);
if (lastreturn != std::string::npos)
grab_to = lastreturn;
else if (lastspace != std::string::npos)
grab_to = lastspace;
std::string out = src.substr(0, grab_to);
src = src.substr(grab_to + 1);
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 } // namespace dropshell

View File

@ -2,6 +2,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <map>
#include "output.hpp" #include "output.hpp"
@ -16,6 +17,8 @@ void maketitle(const std::string& title, sColour colour=sColour::INFO);
bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line); bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line);
std::string magic_string();
bool has_magic_string(std::string name);
// utility functions // utility functions
std::string trim(std::string str); std::string trim(std::string str);
@ -39,8 +42,6 @@ void ensure_directories_exist(std::vector<std::string> directories);
std::vector<int> search(const std::string &pat, const std::string &txt); std::vector<int> search(const std::string &pat, const std::string &txt);
int count_substring(const std::string &substring, const std::string &text); int count_substring(const std::string &substring, const std::string &text);
std::string replace_with_environment_variables_like_bash(std::string str);
std::string random_alphanumeric_string(int length); std::string random_alphanumeric_string(int length);
int die(const std::string & msg); int die(const std::string & msg);
@ -51,4 +52,13 @@ std::string left_align(const std::string & str, int width);
std::string right_align(const std::string & str, int width); std::string right_align(const std::string & str, int width);
std::string center_align(const std::string & str, int width); std::string center_align(const std::string & str, int width);
std::string replace_with_environment_variables_like_bash(std::string str);
std::string substitute_provided_key_value_pairs(std::string str, const std::map<std::string, std::string> & env_vars);
int get_console_width();
std::string get_line_wrap(std::string & src, int maxchars);
std::string tolower(const std::string& str);
} // namespace dropshell } // namespace dropshell