Compare commits

...

35 Commits

Author SHA1 Message Date
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
f89d90c12b dropshell release 2025.0518.1451
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 14:51:53 +12:00
5883c62c54 Tidy output
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 14:29:51 +12:00
b278e81533 info for table print
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 14:03:12 +12:00
6c99b429b9 dropshell release 2025.0518.1356
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 13:56:52 +12:00
2b446f80a3 dropshell release 2025.0518.1355
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 13:55:39 +12:00
314a5fe96a Trying new approach 2025-05-18 13:07:09 +12:00
828171c977 dropshell release 2025.0518.1300
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 13:00:35 +12:00
668cef5a05 . 2025-05-18 12:40:26 +12:00
e7c6d38273 dropshell release 2025.0518.1218
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 12:18:43 +12:00
d0152c3ec7 dropshell release 2025.0518.1150
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 11:50:23 +12:00
ebb101e381 dropshell release 2025.0518.1145
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 11:45:37 +12:00
dc2f694ebe .
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-17 23:12:43 +12:00
038d08e638 dropshell release 2025.0517.2231
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-17 22:31:29 +12:00
27f86e95e7 .
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-17 22:24:25 +12:00
891f0d023f Self-test.
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-17 20:41:17 +12:00
49 changed files with 3109 additions and 1877 deletions

1
.gitignore vendored
View File

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

View File

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.10)
project(dropshell VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Set default build type to Release if not specified
@ -48,10 +49,22 @@ add_custom_target(run_createagent ALL
add_executable(dropshell ${SOURCES})
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
# build dir goes first so that we can use the generated version.hpp
target_include_directories(dropshell PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src/autogen>
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/utils

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

@ -73,6 +73,7 @@ function run_command() {
# update the main variables.
CONFIG_PATH="${service_path}/config"
SERVICE="${SERVICE_NAME}"
DOCKER_CLI_HINTS=false
set +a
@ -99,6 +100,10 @@ function command_exists() {
}
if [ ! -d "${SERVICES_PATH}" ]; then
echo "Services path does not exist: ${SERVICES_PATH}"
exit 0
fi
# Get all service names
SERVICE_NAMES=$(ls "${SERVICES_PATH}")

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

@ -28,20 +28,10 @@
# Prints an error message in red and exits with status code 1.
_die() {
echo -e "\033[91mError: $1\033[0m"
echo -e "Error: $1"
exit 1
}
# Switches terminal output color to grey.
_grey_start() {
echo -e -n "\033[90m"
}
# Resets terminal output color from grey.
_grey_end() {
echo -e -n "\033[0m"
}
# Creates/starts a container, verifying it runs.
_create_and_start_container() {
if [ -z "$1" ] || [ -z "$2" ]; then
@ -55,9 +45,7 @@ _create_and_start_container() {
_is_container_running $container_name && return 0
_start_container $container_name
else
_grey_start
$run_cmd
_grey_end
fi
if ! _is_container_running $container_name; then

View File

@ -12,6 +12,10 @@ _autocommandrun_volume() {
case "$command" in
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}"
docker volume create ${volume_name}
;;
@ -39,6 +43,10 @@ _autocommandrun_path() {
case "$command" in
create)
if [ -d "${path}" ]; then
echo "Path ${path} already exists - unchanged"
return
fi
echo "Creating path ${path}"
mkdir -p ${path}
;;
@ -47,7 +55,7 @@ _autocommandrun_path() {
local path_parent=$(dirname ${path})
local path_child=$(basename ${path})
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
echo "Path ${path} does not exist - nothing to nuke"
fi
@ -61,8 +69,14 @@ _autocommandrun_path() {
fi
;;
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
fi
;;
esac
}
@ -74,6 +88,12 @@ _autocommandrun_file() {
case "$command" in
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)
rm -f ${filepath}
@ -91,7 +111,8 @@ _autocommandrun_file() {
restore)
echo "Restoring file ${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
}

View File

@ -142,8 +142,12 @@ if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
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!"

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
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

@ -1,427 +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: 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"\
"CiAgICAgICAgU0VSVklDRT0iJHtTRVJWSUNFX05BTUV9IgoKICAgICAgICBzZXQgK2EKCiAgICAg"\
"ICAgX2NoZWNrX3JlcXVpcmVkX2Vudl92YXJzX2FsbHNlcnZpY2Vzc3RhdHVzICJDT05GSUdfUEFU"\
"SCIgIlNFUlZFUiIgIlNFUlZJQ0UiICJBR0VOVF9QQVRIIiAiSE9TVF9OQU1FIiAiVEVNUExBVEUi"\
"CgogICAgICAgIGlmIFsgIiRjYXB0dXJlX291dHB1dCIgPSAidHJ1ZSIgXTsgdGhlbgogICAgICAg"\
"ICAgICAjIENhcHR1cmUgYW5kIHJldHVybiBvdXRwdXQKICAgICAgICAgICAgYmFzaCAiJHtzZXJ2"\
"aWNlX3BhdGh9L3RlbXBsYXRlLyR7Y29tbWFuZH0uc2giIDI+JjEKICAgICAgICBlbHNlCiAgICAg"\
"ICAgICAgICMgUnVuIHNpbGVudGx5IGFuZCByZXR1cm4gZXhpdCBjb2RlCiAgICAgICAgICAgIGJh"\
"c2ggIiR7c2VydmljZV9wYXRofS90ZW1wbGF0ZS8ke2NvbW1hbmR9LnNoIiA+IC9kZXYvbnVsbCAy"\
"PiYxCiAgICAgICAgZmkKICAgICkKICAgIENVUlJFTlRfRVhJVF9DT0RFPSQ/Cn0KCmZ1bmN0aW9u"\
"IGNvbW1hbmRfZXhpc3RzKCkgewogICAgbG9jYWwgc2VydmljZV9wYXRoPSQxCiAgICBsb2NhbCBj"\
"b21tYW5kPSQyCiAgICBpZiBbICEgLWYgIiR7c2VydmljZV9wYXRofS90ZW1wbGF0ZS8ke2NvbW1h"\
"bmR9LnNoIiBdOyB0aGVuCiAgICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICByZXR1cm4gMAp9CgoK"\
"CiMgR2V0IGFsbCBzZXJ2aWNlIG5hbWVzClNFUlZJQ0VfTkFNRVM9JChscyAiJHtTRVJWSUNFU19Q"\
"QVRIfSIpCgojIEl0ZXJhdGUgb3ZlciBhbGwgc2VydmljZSBuYW1lcwpmb3IgU0VSVklDRV9OQU1F"\
"IGluICR7U0VSVklDRV9OQU1FU307IGRvCgogICAgU0VSVklDRV9QQVRIPSQocmVhbHBhdGggIiR7"\
"U0VSVklDRVNfUEFUSH0vJHtTRVJWSUNFX05BTUV9IikKCiAgICAjLS0tLS0tLS0tLS0tLS0tLS0t"\
"LS0tLS0tLS0tLS0tLS0KICAgICMgR2V0IHRoZSBzZXJ2aWNlIGhlYWx0aAogICAgaWYgISBjb21t"\
"YW5kX2V4aXN0cyAiJHtTRVJWSUNFX1BBVEh9IiAic3RhdHVzIjsgdGhlbgogICAgICAgIFNFUlZJ"\
"Q0VfSEVBTFRIPSJ1bmtub3duIgogICAgZWxzZQogICAgICAgIHJ1bl9jb21tYW5kICIke1NFUlZJ"\
"Q0VfUEFUSH0iICJzdGF0dXMiICJmYWxzZSIKICAgICAgICBpZiBbICIke0NVUlJFTlRfRVhJVF9D"\
"T0RFfSIgLWVxIDAgXTsgdGhlbgogICAgICAgICAgICBTRVJWSUNFX0hFQUxUSD0iaGVhbHRoeSIK"\
"ICAgICAgICBlbHNlCiAgICAgICAgICAgIFNFUlZJQ0VfSEVBTFRIPSJ1bmhlYWx0aHkiCiAgICAg"\
"ICAgZmkKICAgIGZpCgogICAgIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICAj"\
"IEdldCB0aGUgc2VydmljZSBwb3J0cwogICAgaWYgISBjb21tYW5kX2V4aXN0cyAiJHtTRVJWSUNF"\
"X1BBVEh9IiAicG9ydHMiOyB0aGVuCiAgICAgICAgU0VSVklDRV9QT1JUUz0iIgogICAgZWxzZQog"\
"ICAgICAgIHJ1bl9jb21tYW5kICIke1NFUlZJQ0VfUEFUSH0iICJwb3J0cyIgInRydWUiCiAgICAg"\
"ICAgU0VSVklDRV9QT1JUUz0iJHtDVVJSRU5UX09VVFBVVH0iCiAgICBmaQoKICAgICMtLS0tLS0t"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgIyByZXR1cm4gdGhlIGhlYWx0aCBhbmQgcG9y"\
"dHMKICAgIGVjaG8gIiR7U0VSVklDRV9OQU1FfV9IRUFMVEg9JHtTRVJWSUNFX0hFQUxUSH0iCiAg"\
"ICBlY2hvICIke1NFUlZJQ0VfTkFNRX1fUE9SVFM9JHtTRVJWSUNFX1BPUlRTfSIKZG9uZQo=";
// 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, 4383289270743338040ULL, 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"\
"KCkgewogICAgZWNobyAtZSAiXDAzM1s5MW1FcnJvcjogJDFcMDMzWzBtIgogICAgZXhpdCAxCn0K"\
"CiMgU3dpdGNoZXMgdGVybWluYWwgb3V0cHV0IGNvbG9yIHRvIGdyZXkuCl9ncmV5X3N0YXJ0KCkg"\
"ewogICAgZWNobyAtZSAtbiAiXDAzM1s5MG0iCn0KCiMgUmVzZXRzIHRlcm1pbmFsIG91dHB1dCBj"\
"b2xvciBmcm9tIGdyZXkuCl9ncmV5X2VuZCgpIHsKICAgIGVjaG8gLWUgLW4gIlwwMzNbMG0iCn0K"\
"CiMgQ3JlYXRlcy9zdGFydHMgYSBjb250YWluZXIsIHZlcmlmeWluZyBpdCBydW5zLgpfY3JlYXRl"\
"X2FuZF9zdGFydF9jb250YWluZXIoKSB7CiAgICBpZiBbIC16ICIkMSIgXSB8fCBbIC16ICIkMiIg"\
"XTsgdGhlbgogICAgICAgIF9kaWUgIlRlbXBsYXRlIGVycm9yOiBjcmVhdGVfYW5kX3N0YXJ0X2Nv"\
"bnRhaW5lciA8cnVuX2NtZD4gPGNvbnRhaW5lcl9uYW1lPiIKICAgIGZpCgogICAgbG9jYWwgcnVu"\
"X2NtZD0iJDEiCiAgICBsb2NhbCBjb250YWluZXJfbmFtZT0iJDIiCgogICAgaWYgX2lzX2NvbnRh"\
"aW5lcl9leGlzdHMgJGNvbnRhaW5lcl9uYW1lOyB0aGVuCiAgICAgICAgX2lzX2NvbnRhaW5lcl9y"\
"dW5uaW5nICRjb250YWluZXJfbmFtZSAmJiByZXR1cm4gMAogICAgICAgIF9zdGFydF9jb250YWlu"\
"ZXIgJGNvbnRhaW5lcl9uYW1lCiAgICBlbHNlCiAgICAgICAgX2dyZXlfc3RhcnQKICAgICAgICAk"\
"cnVuX2NtZAogICAgICAgIF9ncmV5X2VuZAogICAgZmkKCiAgICBpZiAhIF9pc19jb250YWluZXJf"\
"cnVubmluZyAkY29udGFpbmVyX25hbWU7IHRoZW4KICAgICAgICBfZGllICJDb250YWluZXIgJHtj"\
"b250YWluZXJfbmFtZX0gZmFpbGVkIHRvIHN0YXJ0IgogICAgZmkKCiAgICBJRD0kKF9nZXRfY29u"\
"dGFpbmVyX2lkICRjb250YWluZXJfbmFtZSkKICAgIGVjaG8gIkNvbnRhaW5lciAke2NvbnRhaW5l"\
"cl9uYW1lfSBpcyBydW5uaW5nIHdpdGggSUQgJHtJRH0iCn0KCiMgQ3JlYXRlcyBhIGRpcmVjdG9y"\
"eSBpZiBpdCBkb2Vzbid0IGV4aXN0IChjaG1vZCA3NzcpLgpfY3JlYXRlX2ZvbGRlcigpIHsKICAg"\
"IGxvY2FsIGZvbGRlcj0iJDEiCiAgICBpZiBbIC1kICIkZm9sZGVyIiBdOyB0aGVuCiAgICAgICAg"\
"cmV0dXJuIDAKICAgIGZpCiAgICBpZiAhIG1rZGlyIC1wICIkZm9sZGVyIjsgdGhlbgogICAgICAg"\
"IF9kaWUgIkZhaWxlZCB0byBjcmVhdGUgZm9sZGVyOiAkZm9sZGVyIgogICAgZmkKICAgIGNobW9k"\
"IDc3NyAiJGZvbGRlciIKICAgIGVjaG8gIkZvbGRlciBjcmVhdGVkOiAkZm9sZGVyIgp9CgojIENo"\
"ZWNrcyBpZiBEb2NrZXIgaXMgaW5zdGFsbGVkLCBydW5uaW5nLCBhbmQgdXNlciBoYXMgcGVybWlz"\
"c2lvbi4gUmV0dXJucyAxIG9uIGZhaWx1cmUuCl9jaGVja19kb2NrZXJfaW5zdGFsbGVkKCkgewog"\
"ICAgaWYgISBjb21tYW5kIC12IGRvY2tlciAmPiAvZGV2L251bGw7IHRoZW4KICAgICAgICBlY2hv"\
"ICJEb2NrZXIgaXMgbm90IGluc3RhbGxlZCIKICAgICAgICByZXR1cm4gMQogICAgZmkKCiAgICAj"\
"IGNoZWNrIGlmIGRvY2tlciBkYWVtb24gaXMgcnVubmluZwogICAgaWYgISBkb2NrZXIgaW5mbyAm"\
"PiAvZGV2L251bGw7IHRoZW4KICAgICAgICBlY2hvICJEb2NrZXIgZGFlbW9uIGlzIG5vdCBydW5u"\
"aW5nIgogICAgICAgIHJldHVybiAxCiAgICBmaQoKICAgICMgY2hlY2sgaWYgdXNlciBoYXMgcGVy"\
"bWlzc2lvbiB0byBydW4gZG9ja2VyCiAgICBpZiAhIGRvY2tlciBydW4gLS1ybSBoZWxsby13b3Js"\
"ZCAmPiAvZGV2L251bGw7IHRoZW4KICAgICAgICBlY2hvICJVc2VyIGRvZXMgbm90IGhhdmUgcGVy"\
"bWlzc2lvbiB0byBydW4gZG9ja2VyIgogICAgICAgIHJldHVybiAxCiAgICBmaQoKICAgIHJldHVy"\
"biAwCn0KCiMgQ2hlY2tzIGlmIGEgY29udGFpbmVyIChhbnkgc3RhdGUpIGV4aXN0cy4gUmV0dXJu"\
"cyAxIGlmIG5vdCBmb3VuZC4KX2lzX2NvbnRhaW5lcl9leGlzdHMoKSB7CiAgICBpZiAhIGRvY2tl"\
"ciBwcyAtYSAtLWZvcm1hdCAie3suTmFtZXN9fSIgfCBncmVwIC1xICJeJDEkIjsgdGhlbgogICAg"\
"ICAgIHJldHVybiAxCiAgICBmaQogICAgcmV0dXJuIDAKfQoKIyBDaGVja3MgaWYgYSBjb250YWlu"\
"ZXIgaXMgY3VycmVudGx5IHJ1bm5pbmcuIFJldHVybnMgMSBpZiBub3QgcnVubmluZy4KX2lzX2Nv"\
"bnRhaW5lcl9ydW5uaW5nKCkgewogICAgaWYgISBkb2NrZXIgcHMgLS1mb3JtYXQgInt7Lk5hbWVz"\
"fX0iIHwgZ3JlcCAtcSAiXiQxJCI7IHRoZW4KICAgICAgICByZXR1cm4gMQogICAgZmkKICAgIHJl"\
"dHVybiAwCn0KCiMgUHJpbnRzIHRoZSBJRCBvZiB0aGUgbmFtZWQgY29udGFpbmVyLgpfZ2V0X2Nv"\
"bnRhaW5lcl9pZCgpIHsKICAgIGRvY2tlciBwcyAtLWZvcm1hdCAie3suSUR9fSIgLS1maWx0ZXIg"\
"Im5hbWU9JDEiCn0KCiMgUHJpbnRzIHRoZSBzdGF0dXMgc3RyaW5nIG9mIHRoZSBuYW1lZCBjb250"\
"YWluZXIuCl9nZXRfY29udGFpbmVyX3N0YXR1cygpIHsKICAgIGRvY2tlciBwcyAtLWZvcm1hdCAi"\
"e3suU3RhdHVzfX0iIC0tZmlsdGVyICJuYW1lPSQxIgp9CgojIFN0YXJ0cyBhbiBleGlzdGluZywg"\
"c3RvcHBlZCBjb250YWluZXIuCl9zdGFydF9jb250YWluZXIoKSB7CiAgICBfaXNfY29udGFpbmVy"\
"X2V4aXN0cyAkMSB8fCByZXR1cm4gMQogICAgX2lzX2NvbnRhaW5lcl9ydW5uaW5nICQxICYmIHJl"\
"dHVybiAwCiAgICBkb2NrZXIgc3RhcnQgJDEKfQoKIyBTdG9wcyBhIHJ1bm5pbmcgY29udGFpbmVy"\
"Lgpfc3RvcF9jb250YWluZXIoKSB7CiAgICBfaXNfY29udGFpbmVyX3J1bm5pbmcgJDEgfHwgcmV0"\
"dXJuIDA7CiAgICBkb2NrZXIgc3RvcCAkMQp9ICAgCgojIFN0b3BzIChpZiBuZWVkZWQpIGFuZCBy"\
"ZW1vdmVzIGEgY29udGFpbmVyLgpfcmVtb3ZlX2NvbnRhaW5lcigpIHsKICAgIF9zdG9wX2NvbnRh"\
"aW5lciAkMQogICAgX2lzX2NvbnRhaW5lcl9leGlzdHMgJDEgfHwgcmV0dXJuIDA7CiAgICBkb2Nr"\
"ZXIgcm0gJDEKfQoKIyBQcmludHMgdGhlIGxvZ3MgZm9yIGEgY29udGFpbmVyLgpfZ2V0X2NvbnRh"\
"aW5lcl9sb2dzKCkgewogICAgaWYgISBfaXNfY29udGFpbmVyX2V4aXN0cyAkMTsgdGhlbgogICAg"\
"ICAgIGVjaG8gIkNvbnRhaW5lciAkMSBkb2VzIG5vdCBleGlzdCIKICAgICAgICByZXR1cm4gMQog"\
"ICAgZmkKCiAgICBkb2NrZXIgbG9ncyAkMQp9CgojIENoZWNrcyBpZiBsaXN0ZWQgZW52aXJvbm1l"\
"bnQgdmFyaWFibGVzIGFyZSBzZXQ7IGNhbGxzIF9kaWUoKSBpZiBhbnkgYXJlIG1pc3NpbmcuCl9j"\
"aGVja19yZXF1aXJlZF9lbnZfdmFycygpIHsKICAgIGxvY2FsIHJlcXVpcmVkX3ZhcnM9KCIkQCIp"\
"CiAgICBmb3IgdmFyIGluICIke3JlcXVpcmVkX3ZhcnNbQF19IjsgZG8KICAgICAgICBpZiBbIC16"\
"ICIkeyF2YXJ9IiBdOyB0aGVuCiAgICAgICAgICAgIF9kaWUgIlJlcXVpcmVkIGVudmlyb25tZW50"\
"IHZhcmlhYmxlICR2YXIgaXMgbm90IHNldCIKICAgICAgICBmaQogICAgZG9uZQp9CgojIFJlbW92"\
"ZXMgYSBwYXRoIHVzaW5nIGEgcm9vdCBEb2NrZXIgY29udGFpbmVyIChmb3IgcGVybWlzc2lvbnMp"\
"Lgpfcm9vdF9yZW1vdmVfdHJlZSgpIHsKICAgIGxvY2FsIHRvX3JlbW92ZT0iJDEiCiAgICBwYXJl"\
"bnQ9JChkaXJuYW1lICIkdG9fcmVtb3ZlIikKICAgIGFic19wYXJlbnQ9JChyZWFscGF0aCAiJHBh"\
"cmVudCIpCiAgICBjaGlsZD0kKGJhc2VuYW1lICIkdG9fcmVtb3ZlIikKICAgIGRvY2tlciBydW4g"\
"LS1ybSAtdiAiJGFic19wYXJlbnQiOi9kYXRhIGFscGluZSBybSAtcmYgIi9kYXRhLyRjaGlsZCIK"\
"fQoKCiMgTG9hZCBhdXRvY29tbWFuZHMKc291cmNlICIke0FHRU5UX1BBVEh9L2RhdGFjb21tYW5k"\
"cy5zaCI=";
// 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, 15958097326741776083ULL, 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

@ -0,0 +1,174 @@
#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 "server_env_manager.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 std::string &server, const std::string &service)
{
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
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.
}
// Check if basic installed stuff is in place.
std::string remote_service_template_path = remotepath::service_template(server, service);
std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
std::string remote_service_config_path = remotepath::service_config(server, service);
if (!server_env.check_remote_items_exist({remotepath::service(server, service),
remote_command_script_file,
remotefile::service_env(server, service)}))
{
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::backups(server);
debug << "Remote backups directory on " << server << ": " << remote_backups_dir << std::endl;
std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Defaults))
{
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);
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))
{
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,44 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/output.hpp"
#include "utils/assert.hpp"
namespace dropshell {
void colours_autocomplete(const CommandContext& ctx) {}
int colours_handler(const CommandContext& ctx)
{
info << "Colours:" << std::endl;
debug << "Debug Example: The quick brown fox jumps over the lazy dog." << std::endl;
info << "Info Example: The quick brown fox jumps over the lazy dog." << std::endl;
warning << "Warning Example: The quick brown fox jumps over the lazy dog." << std::endl;
error << "Error Example: The quick brown fox jumps over the lazy dog." << std::endl;
return 0;
}
static std::vector<std::string> colours_name_list={"colours","c","--colours","-c"};
// Static registration
struct ColoursCommandRegister {
ColoursCommandRegister() {
CommandRegistry::instance().register_command({
colours_name_list,
colours_handler,
colours_autocomplete,
true, // hidden
false, // requires_config
false, // requires_install
0, // min_args (after command)
0, // max_args (after command)
"colours",
"Show the colours used by dropshell.",
// heredoc
R"(
Show the colours used by dropshell.
)"
});
}
} colours_command_register;
} // 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,19 +7,20 @@
#include "utils/utils.hpp"
#include "services.hpp"
#include <fstream>
namespace dropshell
{
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);
static std::vector<std::string> create_service_name_list = {"create-service"};
// Static registration
struct UninstallCommandRegister
struct CreateServiceCommandRegister
{
UninstallCommandRegister()
CreateServiceCommandRegister()
{
CommandRegistry::instance().register_command({create_service_name_list,
create_service_handler,
@ -45,7 +46,7 @@ namespace dropshell
std::string service = safearg(ctx.args, 1);
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)
@ -58,12 +59,48 @@ namespace dropshell
{
std::set<std::string> templates = gTemplateManager().get_template_list();
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)
{
if (server_name.empty() || template_name.empty() || service_name.empty())
return false;
@ -72,44 +109,34 @@ namespace dropshell
if (service_dir.empty())
{
if (!silent)
{
std::cerr << "Error: Couldn't locate server " << server_name << " in any config directory" << std::endl;
std::cerr << "Please check the server name is correct and try again" << 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;
}
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;
info << "You can list all servers with 'dropshell servers'" << std::endl;
info << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
return false;
}
if (std::filesystem::exists(service_dir))
{
if (!silent)
{
std::cerr << "Error: Service already exists: " << service_name << std::endl;
std::cerr << "Current service path: " << service_dir << std::endl;
}
error << "Service already exists: " << service_name << std::endl;
debug << "Current service path: " << service_dir << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(template_name);
if (!tinfo.is_set())
{
if (!silent)
{
std::cerr << "Error: Template '" << template_name << "' not found" << std::endl;
std::cerr << "Please check the template name is correct and try again" << 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;
}
error << "Template '" << template_name << "' not found" << std::endl;
info << "Please check the template name is correct and try again" << std::endl;
info << "You can list all templates with 'dropshell templates'" << std::endl;
info << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
return false;
}
// check template is all good.
if (!gTemplateManager().test_template(tinfo.local_template_path()))
{
if (!silent)
std::cerr << "Error: Template '" << template_name << "' is not valid" << std::endl;
error << "Template '" << template_name << "' is not valid" << std::endl;
return false;
}
@ -119,15 +146,18 @@ namespace dropshell
// copy the template config files to the service directory
recursive_copy(tinfo.local_template_path() / "config", service_dir);
if (!silent)
info << "Service " << service_name << " created successfully" << std::endl;
if (!print_readme(tinfo, server_name, service_name))
{
std::cout << "Service " << service_name << " created successfully" << std::endl;
std::cout << std::endl;
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;
std::cout << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
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;
}
} // namespace shared_commands
} // 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,154 @@
#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 = {"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)
{
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 << "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 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

@ -66,16 +66,16 @@ bool edit_file(const std::string &file_path, bool has_bb64)
// Check if stdin is connected to a terminal if EDITOR is not set
editor_cmd = "nano -w " + quote(file_path);
} else {
std::cerr << "Error: Standard input is not a terminal and EDITOR environment variable is not set." << std::endl;
std::cerr << "Try setting the EDITOR environment variable (e.g., export EDITOR=nano) or run in an interactive terminal." << std::endl;
std::cerr << "You can manually edit the file at: " << file_path << std::endl;
error << "Standard input is not a terminal and EDITOR environment variable is not set." << std::endl;
info << "Try setting the EDITOR environment variable (e.g., export EDITOR=nano) or run in an interactive terminal." << std::endl;
info << "You can manually edit the file at: " << file_path << std::endl;
return false;
}
std::cout << "Editing file: " << file_path << std::endl;
info << "Editing file: " << file_path << std::endl;
if (has_bb64) {
return execute_local_command(editor_cmd, nullptr, cMode::Interactive);
return execute_local_command("", editor_cmd, {}, nullptr, cMode::Interactive);
}
else {
// might not have bb64 at this early stage. Direct edit.
@ -119,14 +119,14 @@ int edit_server(const std::string &server_name)
std::string config_file = localfile::server_json(server_name);
if (!edit_file(config_file, true)) {
std::cerr << "Error: Failed to edit server config" << std::endl;
std::cerr << "You can manually edit this file at: " << config_file << std::endl;
error << "Failed to edit server config" << std::endl;
info << "You can manually edit this file at: " << config_file << std::endl;
return 1;
}
std::cout << "If you have changed DROPSHELL_DIR, you should manually move the files to the new location NOW." << std::endl
<< "You can ssh in to the remote server with: dropshell ssh "<<server_name<< std::endl
<< "Once moved, reinstall all services with: dropshell install " << server_name << std::endl;
info << "If you have changed DROPSHELL_DIR, you should manually move the files to the new location NOW." << std::endl;
info << "You can ssh in to the remote server with: dropshell ssh "<<server_name<< std::endl;
info << "Once moved, reinstall all services with: dropshell install " << server_name << std::endl;
return 0;
}
@ -138,12 +138,12 @@ int edit_service_config(const std::string &server, const std::string &service)
std::string config_file = localfile::service_env(server, service);
if (!std::filesystem::exists(config_file))
{
std::cerr << "Error: Service config file not found: " << config_file << std::endl;
error << "Service config file not found: " << config_file << std::endl;
return 1;
}
if (edit_file(config_file, true) && std::filesystem::exists(config_file))
std::cout << "To apply your changes, run:\n dropshell install " + server + " " + service << std::endl;
info << "To apply your changes, run:\n dropshell install " + server + " " + service << std::endl;
return 0;
}
@ -167,7 +167,7 @@ int edit_handler(const CommandContext& ctx) {
return 0;
}
std::cout << "Edit handler called with " << ctx.args.size() << " args\n";
info << "Edit handler called with " << ctx.args.size() << " args\n";
return -1;
}

View File

@ -45,7 +45,7 @@ namespace dropshell
{
if (ctx.args.size() < 1)
{
std::cerr << "Error: Server name is required" << std::endl;
error << "Server name is required" << std::endl;
return 1;
}

View File

@ -45,23 +45,44 @@ struct HelpCommandRegister {
void help_autocomplete(const CommandContext& ctx) {
if (ctx.args.size() == 1) {
if (ctx.args.size() == 0) {
// list all commands
for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) {
std::cout << cmd << std::endl;
rawout << cmd << std::endl;
}
}
return;
}
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);
if (!cmd_info)
std::cout << "Unknown command: " << cmd << std::endl;
{
error << "Unknown command: " << cmd << std::endl;
return;
}
std::cout << " ";
print_left_aligned(cmd_info->help_usage, 30);
std::cout << cmd_info->help_description << std::endl;
if (cmd_info->help_usage.length() < width-secondcol)
{
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;
@ -74,28 +95,26 @@ int show_command_help(const std::string& cmd) {
const auto& cmd_info = CommandRegistry::instance().find_command(cmd);
if (!cmd_info)
{
std::cout << "Unknown command: " << cmd << std::endl;
error << "Unknown command: " << cmd << std::endl;
return 1;
}
info << "Command " << cmd << " usage:" << std::endl;
info << " ";
info << left_align(cmd_info->help_usage, 32);
info << cmd_info->help_description << std::endl;
std::cout << std::endl;
std::cout << "Usage: " << std::endl;
std::cout << " ";
print_left_aligned(cmd_info->help_usage, 30);
std::cout << cmd_info->help_description << std::endl;
info << std::endl;
std::cout << std::endl;
std::cout << " Equivalent names: ";
info << " Equivalent names: ";
bool first = true;
for (const auto& name : cmd_info->names) {
if (!first) std::cout << ", ";
std::cout << name;
if (!first) info << ", ";
info << name;
first = false;
}
std::cout << std::endl << std::endl;
info << std::endl;
std::cout << cmd_info->full_help << std::endl << std::endl;
info << cmd_info->full_help << std::endl << std::endl;
return 0;
}
@ -107,10 +126,10 @@ int help_handler(const CommandContext& ctx) {
std::cout << std::endl;
maketitle("DropShell version " + VERSION);
std::cout << std::endl;
std::cout << "A tool for managing remote servers, by " << AUTHOR << std::endl;
std::cout << std::endl;
std::cout << "dropshell ..." << std::endl;
info << std::endl;
info << "A tool for managing remote servers, by " << AUTHOR << std::endl;
info << std::endl;
info << "dropshell ..." << std::endl;
show_command("help");
show_command("edit");
@ -119,60 +138,22 @@ int help_handler(const CommandContext& ctx) {
{
// show more!
show_command("list");
info << std::endl;
show_command("install");
show_command("uninstall");
show_command("nuke");
info << std::endl;
show_command("start");
show_command("stop");
info << std::endl;
show_command("ssh");
info << std::endl;
show_command("create-server");
show_command("create-service");
}
return 0;
}
// void show_command(const std::string& cmd) {
// const auto& cmd_info = CommandRegistry::instance().find_command(cmd);
// if (cmd_info) {
// std::cout << " " << cmd_info->help_usage
// << std::string(' ', std::min(1,(int)(30-cmd_info->help_usage.length())))
// << cmd_info->help_description << std::endl;
// }
// }
// bool print_help() {
// std::cout << std::endl;
// maketitle("DropShell version " + VERSION);
// std::cout << std::endl;
// std::cout << "A tool for managing server configurations" << std::endl;
// std::cout << std::endl;
// std::cout << "dropshell ..." << std::endl;
// show_command("help");
// show_command("edit");
// if (gConfig().is_config_set()) {
// std::cout << " templates List all available templates" << std::endl;
// std::cout << std::endl;
// std::cout << std::endl;
// std::cout << "Service commands: (if no service is specified, all services for the server are affected)" << std::endl;
// std::cout << " list [SERVER] [SERVICE] List status/details of all servers/server/service." << std::endl;
// std::cout << " edit [SERVER] [SERVICE] Edit the configuration of dropshell/server/service." << std::endl;
// std::cout << std::endl;
// std::cout << " install SERVER [SERVICE] Install/reinstall/update service(s). Safe/non-destructive." << std::endl;
// std::cout << " uninstall SERVER [SERVICE] Uninstalls the service on the remote server. Leaves data intact." << std::endl;
// std::cout << " nuke SERVER SERVICE Nuke the service, deleting ALL local and remote data." << std::endl;
// std::cout << std::endl;
// std::cout << " COMMAND SERVER [SERVICE] Run a command on service(s), e.g." << std::endl;
// std::cout << " backup, restore, start, stop, logs" << std::endl;
// std::cout << std::endl;
// std::cout << " ssh SERVER SERVICE Launch an interactive shell on a server or service" << std::endl;
// std::cout << std::endl;
// std::cout << "Creation commands: (apply to the first local config directory)"<<std::endl;
// std::cout << " create-template TEMPLATE" << std::endl;
// std::cout << " create-server SERVER" << std::endl;
// std::cout << " create-service SERVER TEMPLATE SERVICE" << std::endl;
// }
// else {
// show_command("help");
// show_command("edit");
// std::cout << std::endl;
// std::cout << "Other commands available once initialised." << std::endl;
// }
// return true;
// }
} // namespace dropshell

View File

@ -5,15 +5,19 @@
#include "templates.hpp"
#include "shared_commands.hpp"
#include "utils/hash.hpp"
#include "autogen/_agent.hpp"
#include "autogen/_agent-local.hpp"
#include "autogen/_agent-remote.hpp"
#include "services.hpp"
#include "utils/output.hpp"
#include <fstream>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/assert.hpp"
#include "servers.hpp"
namespace dropshell
{
@ -35,15 +39,15 @@ namespace dropshell
false, // requires_install
0, // min_args (after command)
2, // max_args (after command)
"install SERVER [SERVICE|all]",
"Install/reinstall service(s). Safe/non-destructive way to update.",
"install [SERVER] [SERVICE|all]",
"Install/reinstall host and remote servers, or service(s). Safe way to update.",
// heredoc
R"(
Install components on a server. This is safe to re-run (non-destructive) and used to update
servers and their services.
install (re)install dropshell components on this computer.
install SERVER (re)install dropshell agent on the given server.
install (re)install dropshell components on this computer, and on all servers.
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.
Note you need to create the service first with:
@ -53,10 +57,13 @@ namespace dropshell
} install_command_register;
namespace shared_commands
{
// ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool install_service(const std::string &server, const std::string &service, bool silent)
bool install_service(const std::string &server, const std::string &service)
{
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
@ -100,20 +107,20 @@ namespace dropshell
}
// Copy template files
std::cout << "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;
if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service),
server_env, silent))
server_env, false))
{
std::cerr << "Failed to copy template files using rsync" << std::endl;
return false;
}
// Copy service files
std::cout << "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;
if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service),
server_env, silent))
server_env, false))
{
std::cerr << "Failed to copy service files using rsync" << std::endl;
return false;
@ -121,14 +128,18 @@ namespace dropshell
// Run install script
{
server_env.run_remote_template_command(service, "install", {}, silent, {});
info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, false, {});
}
// print health tick
std::cout << "Health: " << shared_commands::healthtick(server, service) << std::endl;
info << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true;
}
} // namespace shared_commands
// ------------------------------------------------------------------------------------------------
// update_dropshell
@ -154,6 +165,8 @@ namespace dropshell
int update_dropshell()
{
maketitle("Updating dropshell on this computer...");
// determine path to this executable
std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe");
std::filesystem::path parent_path = dropshell_path.parent_path();
@ -197,8 +210,8 @@ namespace dropshell
if (currentver >= newver)
{
std::cout << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl;
std::cout << "No update needed." << std::endl;
info << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl;
info << "Release version is not newer, no update needed." << std::endl;
return 0;
}
@ -209,57 +222,83 @@ namespace dropshell
rval = system(bash_script_2.c_str());
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;
}
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
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;
}
int install_local_agent()
{
std::vector<std::filesystem::path> paths = {
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);
maketitle("Installing dropshell agent on this computer...");
for (auto &p : paths)
if (!std::filesystem::exists(p))
{
std::cout << "Creating directory: " << p << std::endl;
std::filesystem::create_directories(p);
}
// clear out old cruft.
std::filesystem::remove_all(localpath::agent_local());
std::filesystem::remove_all(localpath::agent_remote());
// download bb64 for the host architecture.
if (!std::filesystem::exists(localpath::agent() + "bb64"))
{
std::string cmd = "cd " + localpath::agent() + " && curl -fsSL -o bb64 https://gitea.jde.nz/public/bb64/releases/download/latest/bb64.amd64 && chmod a+x bb64";
int ret = system(cmd.c_str());
if (EXITSTATUSCHECK(ret))
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.
}
// recreate the directories.
localpath::create_directories();
std::cout << "Creating local files to copy to remote agents..." << std::endl;
recreate_agent::recreate_tree(localpath::files_for_remote_agent());
// populate the agent-local directory.
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;
}
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::agent_remote(), agent_path, server_env, false);
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_env.get_SSH_INFO(), sCommand(agent_path, "agent-install.sh",{}), cMode::Defaults | cMode::NoBB64, nullptr);
if (!okay)
{
error << "ERROR: Failed to install remote agent on " << server << std::endl;
return 1;
}
info << "Installation on " << server << " complete." << std::endl;
return 0;
}
// ------------------------------------------------------------------------------------------------
// install_host
// ------------------------------------------------------------------------------------------------
int install_host()
{
// update dropshell.
@ -273,60 +312,19 @@ namespace dropshell
if (rval != 0)
return rval;
// install the dropshell agent on all servers.
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto &server : servers)
{
rval = install_server(server.name);
if (rval != 0)
return rval;
}
std::cout << "Installation complete." << std::endl;
return 0;
}
int install_server(const std::string &server)
{
// install the dropshell agent on the given server.
std::cout << "Installing dropshell agent on " << server << std::endl;
std::string agent_path = remotepath::agent(server);
if (agent_path.empty())
{
std::cerr << "Failed to get agent path for " << server << std::endl;
return 1;
}
server_env_manager server_env(server);
if (!server_env.is_valid())
{
std::cerr << "Invalid server environment for " << server << std::endl;
return 1;
}
// now create the agent.
// copy across from the local agent files.
shared_commands::rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false);
// add in bb64. We can't use execute_remote_command() here, as that relies on bb64 which we're installing!
std::cout << "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))
std::cerr << "Failed to download bb64 to " << agent_path << " on remote server." << std::endl;
else
std::cout << "Downloaded bb64 to " << agent_path << " on remote server." << std::endl;
// just test all is ok
std::string output;
bool okay = execute_ssh_command(server_env.get_SSH_INFO(), sCommand(agent_path, "./bb64 -i VGhlIGRyb3BzaGVsbCByZW1vdGUgYWdlbnQgaXMgY29ycmVjdGx5IGluc3RhbGxlZC4=", {}), cMode::CaptureOutput, &output);
if (!okay)
{
std::cerr << "Failed to install bb64 on " << server << std::endl;
return 1;
}
std::cout << output << std::endl;
return 0;
}
// ------------------------------------------------------------------------------------------------
// install command implementation
// ------------------------------------------------------------------------------------------------
@ -339,7 +337,7 @@ namespace dropshell
if (!gConfig().is_config_set())
{
std::cerr << "Error: Dropshell is not configured. Please run 'dropshell edit' to configure it." << std::endl;
error << "Dropshell is not configured. Please run 'dropshell edit' to configure it." << std::endl;
return 1;
}
@ -359,7 +357,7 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
{
if (!install_service(server, service.service_name, false))
if (!shared_commands::install_service(server, service.service_name))
okay = false;
}
return okay ? 0 : 1;
@ -367,7 +365,7 @@ namespace dropshell
else
{ // install the specific service.
std::string service = safearg(ctx.args, 1);
return install_service(server, service, false) ? 0 : 1;
return shared_commands::install_service(server, service) ? 0 : 1;
}
}

View File

@ -7,6 +7,7 @@
#include "tableprint.hpp"
#include "transwarp.hpp"
#include "server_env_manager.hpp"
#include "services.hpp"
#include <unistd.h>
#include <cstring>
@ -62,7 +63,7 @@ int list_handler(const CommandContext& ctx) {
return 0;
}
std::cout << "List handler called with " << ctx.args.size() << " args\n";
debug << "List handler called with " << ctx.args.size() << " args\n";
return 0;
}
@ -73,16 +74,16 @@ void list_servers() {
auto servers = get_configured_servers();
if (servers.empty()) {
std::cout << "No servers found" << std::endl;
std::cout << "Please run 'dropshell edit' to set up dropshell." << std::endl;
std::cout << "Then run 'dropshell create-server' to create a server." << std::endl;
error << "No servers found" << std::endl;
info << "Please run 'dropshell edit' to set up dropshell." << std::endl;
info << "Then run 'dropshell create-server' to create a server." << std::endl;
return;
}
tableprint tp("All DropShell Servers");
tp.add_row({"Name", "User", "Address", "Health", "Ports"});
std::cout << "Checking "<<servers.size() << " servers: " << std::flush;
info << "Checking "<<servers.size() << " servers: " << std::flush;
int checked = 0;
transwarp::parallel exec{servers.size()};
@ -102,10 +103,10 @@ void list_servers() {
tp.add_row({server.name, server.ssh_user, server.ssh_host, serviceticks, ports_used_str});
++checked;
// print out a tick character for each server checked.
std::cout << checked << "" << std::flush;
info << checked << "" << std::flush;
});
task->wait();
std::cout << std::endl << std::endl;
info << std::endl << std::endl;
tp.print();
}
@ -115,7 +116,7 @@ void list_servers() {
void show_server_details(const std::string& server_name) {
server_env_manager env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
error << "Error: Invalid server environment file: " << server_name << std::endl;
return;
}
@ -125,14 +126,14 @@ void show_server_details(const std::string& server_name) {
std::string ssh_user = env.get_SSH_USER();
std::string ssh_port = env.get_SSH_PORT();
if (!ssh_address.empty()) {
std::cout << std::endl << "Server Status:" << std::endl;
std::cout << std::string(40, '-') << std::endl;
info << std::endl << "Server Status:" << std::endl;
info << std::string(40, '-') << std::endl;
// Try to connect to the server
std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " 'echo connected' 2>/dev/null";
std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " 'true' 2>/dev/null";
int result = system(cmd.c_str());
if (result == 0) {
std::cout << "Status: Online" << std::endl;
info << "Status: Online" << std::endl;
// // Get uptime if possible
// cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null";
@ -141,10 +142,10 @@ void show_server_details(const std::string& server_name) {
// std::cout << "Error: Failed to get uptime" << std::endl;
// }
} else {
std::cout << "Status: Offline" << std::endl;
warning << "Status: Offline" << std::endl;
}
}
std::cout << std::endl;
info << std::endl;
//---------------------
{
@ -161,7 +162,7 @@ void show_server_details(const std::string& server_name) {
// list services, and run healthcheck on each
{
tableprint tp("Services: " + server_name, false);
tp.add_row({"Status", "Service", "Ports"});
tp.add_row({"Status", "Service", "Template","Ports"});
std::map<std::string, shared_commands::ServiceStatus> status = shared_commands::get_all_services_status(server_name);
@ -175,7 +176,7 @@ void show_server_details(const std::string& server_name) {
for (const auto& port : service_status.ports)
ports_str += std::to_string(port) + " ";
tp.add_row({healthy, service_name, ports_str});
tp.add_row({healthy, service_name, get_service_info(server_name,service_name).template_name, ports_str});
} // end of for (const auto& service : services)
tp.print();
} // end of list services

View File

@ -1,141 +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"
#pragma message ("TODO: Fix issues with Nuke below.")
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 on a server. Destroys everything, both local and remote!
nuke SERVER SERVICE nuke the given service on the given server.
nuke SERVER all nuke all services on the given server.
)"
});
}
} 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))
std::cerr << "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"))
{
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
std::cerr << "Warning: Failed to run nuke script: " << service << std::endl;
}
else
{
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
std::cerr << "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");
std::cout << "Remote service directory removed: " << remotepath::service(server, service) << std::endl;
}
else
std::cerr << "Warning: Failed to remove remote service directory" << std::endl;
}
else
std::cerr << "Warning: Service not found on remote server: " << remotepath::service(server, service) << std::endl;
}
else
std::cerr << "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))
{
std::cerr << "Warning: Local service directory not found: " << local_service_path << std::endl;
}
else
{
auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0)
std::cerr << "Error: Failed to remove local service directory" << std::endl;
}
std::cout << "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())
{
std::cerr << "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,265 @@
#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 "server_env_manager.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];
server_env_manager 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))
return 1;
}
{ // installing fresh service
info << "4) Install of fresh service..." << std::endl;
if (!shared_commands::install_service(server, service))
return 1;
}
{ // restore service from backup
info << "5) Restoring service data from backup..." << std::endl;
std::string remote_backups_dir = remotepath::backups(server);
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))
{
error << "Failed to copy backup file from local to server" << std::endl;
return 1;
}
shared_commands::cRemoteTempFolder remote_temp_folder(server_env);
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

@ -5,6 +5,7 @@
#include "directories.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "utils/output.hpp"
namespace dropshell
{
@ -23,7 +24,7 @@ namespace dropshell
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto &server : servers)
{
std::cout << server.name << std::endl;
rawout << server.name << std::endl;
}
}
else if (ctx.args.size() == 1)
@ -32,7 +33,7 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]);
for (const auto &service : services)
{
std::cout << service.service_name << std::endl;
rawout << service.service_name << std::endl;
}
}
}
@ -44,7 +45,7 @@ namespace dropshell
{
std_autocomplete(ctx);
if (ctx.args.size() == 1)
std::cout << "all" << std::endl;
rawout << "all" << std::endl;
}
// ------------------------------------------------------------------------------------------------
@ -62,7 +63,7 @@ namespace dropshell
quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" +
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));
}
// ------------------------------------------------------------------------------------------------
@ -88,7 +89,7 @@ namespace dropshell
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10);
std::string mkdir_cmd = "mkdir -p " + quote(p);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
std::cerr << "Failed to create temp directory on server" << std::endl;
error << "Failed to create temp directory on server" << std::endl;
else
mPath = p;
}
@ -114,12 +115,14 @@ namespace dropshell
server_env_manager env(server_name);
if (!env.is_valid())
{
std::cerr << "Error: Invalid server environment" << std::endl;
error << "Invalid server environment" << std::endl;
return status;
}
std::string output;
if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}), cMode::CaptureOutput, &output))
if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}),
cMode::Silent,
&output))
return status;
std::stringstream ss(output);
@ -162,8 +165,6 @@ namespace dropshell
return status;
}
// ------------------------------------------------------------------------------------------------
// healthtick : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
@ -202,7 +203,6 @@ namespace dropshell
return ":error:";
}
// ------------------------------------------------------------------------------------------------
// is_healthy : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
@ -211,7 +211,7 @@ namespace dropshell
server_env_manager env(server);
if (!env.is_valid())
{
std::cerr << "Error: Server service not initialized" << std::endl;
error << "Server service not initialized" << std::endl;
return HealthStatus::ERROR;
}
@ -232,7 +232,6 @@ namespace dropshell
return HealthStatus::HEALTHY;
}
// ------------------------------------------------------------------------------------------------
// healthmark : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
@ -242,6 +241,91 @@ namespace dropshell
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 server_env_manager &server_env, const std::string &local_path, const std::string &remote_path, bool silent)
{
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) + " " + server_env.get_SSH_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 server_env_manager &server_env, const std::string &remote_path, const std::string &local_path, bool silent)
{
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() + " " + server_env.get_SSH_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 dropshell

View File

@ -57,6 +57,44 @@ namespace dropshell
void std_autocomplete(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 server_env_manager &server_env, const std::string &local_path, const std::string &remote_path, bool silent);
bool scp_file_from_remote(const server_env_manager &server_env, const std::string &remote_path, const std::string &local_path, bool silent);
// defined in backupdata.cpp, used by restoredata.cpp.
bool backupdata_service(const std::string& server, const std::string& service);
// defined in uninstall.cpp
bool uninstall_service(const std::string &server, 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 std::string &server, 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);
} // namespace shared_commands
} // namespace dropshell

109
source/src/commands/ssh.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "server_env_manager.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "templates.hpp"
namespace dropshell
{
int ssh_handler(const CommandContext &ctx);
static std::vector<std::string> ssh_name_list = {"ssh"};
// Static registration
struct SSHCommandRegister
{
SSHCommandRegister()
{
CommandRegistry::instance().register_command({ssh_name_list,
ssh_handler,
shared_commands::std_autocomplete,
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (after command)
2, // max_args (after command)
"ssh SERVER",
"SSH into a server, or into a docker container for a service.",
R"(
ssh SERVER SERVICE SSH into a docker container for a service.
ssh SERVER SSH into a server.
)"});
}
} ssh_command_register;
bool ssh_into_server(const std::string &server)
{
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
execute_ssh_command(server_env.get_SSH_INFO(), sCommand(remotepath::DROPSHELL_DIR(server), "ls --color && bash", {}), cMode::Interactive);
return true;
}
bool ssh_into_service(const std::string &server, const std::string &service)
{
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
LocalServiceInfo sinfo = get_service_info(server, service);
if (!SIvalid(sinfo))
{
error << "Service " << service << " is not valid" << std::endl;
return false;
}
if (!gTemplateManager().has_template(sinfo.template_name))
{
error << "Template " << sinfo.template_name << " is not valid" << std::endl;
return false;
}
if (!gTemplateManager().template_command_exists(sinfo.template_name, "ssh"))
{
error << "Template " << sinfo.template_name << " does not have an ssh command" << std::endl;
return false;
}
server_env.run_remote_template_command(service,"ssh",{},false,{}); // explicitly supports interactive ssh!
return true;
}
int ssh_handler(const CommandContext &ctx)
{
if (ctx.args.size() < 1)
{
error << "Server name is required" << std::endl;
return 1;
}
std::string server = safearg(ctx.args, 0);
if (ctx.args.size() < 2)
{
// ssh into the server
return ssh_into_server(server) ? 0 : 1;
}
std::string service = safearg(ctx.args, 1);
// ssh into the specific service.
return ssh_into_service(server, service) ? 0 : 1;
}
} // namespace dropshell

View File

@ -0,0 +1,92 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "server_env_manager.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "utils/output.hpp"
namespace dropshell
{
int start_handler(const CommandContext &ctx);
static std::vector<std::string> start_name_list = {"start", "start-service"};
// Static registration
struct StartCommandRegister
{
StartCommandRegister()
{
CommandRegistry::instance().register_command({start_name_list,
start_handler,
shared_commands::std_autocomplete_allowall,
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (after command)
2, // max_args (after command)
"start SERVER SERVICE|all",
"Start a service or all services on a server.",
R"(
start SERVER SERVICE Starts the given service on the given server.
start SERVER all Starts all services on the given server.
Note: This command will not create any data or configuration.
It will simply start the service on the remote server.
Stop the service with stop, or uninstall with uninstall.
)"});
}
} start_command_register;
bool start_service(const std::string &server, const std::string &service)
{
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
// run the start script.
bool started = server_env.run_remote_template_command(service, "start", {}, false, {});
if (started)
{
info << "Service " << service << " on server " << server << " started." << std::endl;
return true;
}
error << "Failed to start service " << service << " on server " << server << std::endl;
return false;
}
int start_handler(const CommandContext &ctx)
{
if (ctx.args.size() < 2)
{
std::cerr << "Error: Server name and service name are both required" << std::endl;
return 1;
}
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
// install all services on the server
maketitle("Stopping all services on " + server);
bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
okay &= start_service(server, service.service_name);
return okay ? 0 : 1;
}
// start the specific service.
return start_service(server, service) ? 0 : 1;
}
} // namespace dropshell

View File

@ -0,0 +1,92 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
#include "server_env_manager.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "utils/output.hpp"
namespace dropshell
{
int stop_handler(const CommandContext &ctx);
static std::vector<std::string> stop_name_list = {"stop", "stop-service"};
// Static registration
struct StopCommandRegister
{
StopCommandRegister()
{
CommandRegistry::instance().register_command({stop_name_list,
stop_handler,
shared_commands::std_autocomplete_allowall,
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (after command)
2, // max_args (after command)
"stop SERVER SERVICE|all",
"Stop a service or all services on a server.",
R"(
stop SERVER SERVICE Stops the given service on the given server.
stop SERVER all Stops all services on the given server.
Note: This command will not destroy any data or configuration.
It will simply stop the service on the remote server.
Restart the service with start, or update and start it with install.
)"});
}
} stop_command_register;
bool stop_service(const std::string &server, const std::string &service)
{
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
// run the stop script.
bool stopped = server_env.run_remote_template_command(service, "stop", {}, false, {});
if (stopped)
{
info << "Service " << service << " on server " << server << " stopped." << std::endl;
return true;
}
error << "Failed to stop service " << service << " on server " << server << std::endl;
return false;
}
int stop_handler(const CommandContext &ctx)
{
if (ctx.args.size() < 2)
{
error << "Server name and service name are both required" << std::endl;
return 1;
}
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
// install all services on the server
maketitle("Stopping all services on " + server);
bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
okay &= stop_service(server, service.service_name);
return okay ? 0 : 1;
}
// stop the specific service.
return stop_service(server, service) ? 0 : 1;
}
} // namespace dropshell

View File

@ -31,62 +31,59 @@ namespace dropshell
"Uninstall a service on a server. Does not remove configuration or user data.",
// heredoc
R"(
Uninstall a service on a server. Does not remove configuration or user data.
uninstall SERVER SERVICE uninstall the given service on the given server.
uninstall SERVER all uninstall all services on the given server.
Uninstall a service, leaving all configuration and data intact.
uninstall SERVER SERVICE Uninstall the given service on the given server.
uninstall SERVER all Uninstall all services on the given server.
Update and reinstall the service with install, or delete all configuration and data with nuke.
)"});
}
} uninstall_command_register;
bool uninstall_service(const std::string &server, const std::string &service, bool silent=false)
namespace shared_commands {
bool uninstall_service(const std::string &server, const std::string &service)
{
if (!silent)
maketitle("Uninstalling " + service + " on " + server);
server_env_manager server_env(server);
if (!server_env.is_valid())
{
std::cerr << "Invalid server: " << server << std::endl;
error << "Invalid server: " << server << std::endl;
return false; // should never hit this.
}
// 2. Check if service directory exists on server
if (!server_env.check_remote_dir_exists(remotepath::service(server, service)))
{
std::cerr << "Service is not installed: " << service << std::endl;
error << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall
}
// 3. Run uninstall script if it exists
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, silent, {}))
if (!silent)
std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Uninstall script failed, but continuing with directory removal" << std::endl;
// 4. Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), silent))
if (server_env.remove_remote_dir(remotepath::service(server, service), false))
{
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
if (!silent)
std::cout << "Removed remote service directory " << remotepath::service(server, service) << std::endl;
info << "Removed remote service directory " << remotepath::service(server, service) << std::endl;
}
else if (!silent)
std::cerr << "Warning: Failed to remove remote service directory" << std::endl;
else
warning << "Failed to remove remote service directory" << std::endl;
if (!silent)
std::cout << "Completed service " << service << " uninstall on " << server << std::endl;
info << "Completed service " << service << " uninstall on " << server << std::endl;
return true;
}
} // namespace shared_commands
int uninstall_handler(const CommandContext &ctx)
{
if (ctx.args.size() < 1)
{
std::cerr << "Error: uninstall requires a server and a service (or all)" << std::endl;
error << "uninstall requires a server and a service (or all)" << std::endl;
return 1;
}
@ -99,15 +96,14 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
{
if (!uninstall_service(server, service.service_name))
if (!shared_commands::uninstall_service(server, service.service_name))
okay = false;
}
return okay ? 0 : 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

View File

@ -6,6 +6,9 @@
#include "utils/json.hpp"
#include <filesystem>
#include "utils/execute.hpp"
#include "output.hpp"
namespace dropshell {
@ -43,6 +46,15 @@ bool config::load_config() { // load json config file.
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)
{
std::string config_path = localfile::dropshell_json();
@ -58,37 +70,27 @@ bool config::save_config(bool create_aux_directories)
if (!mIsConfigSet)
{
std::string homedir = localpath::current_user_home();
std::string dropshell_base = homedir + "/.dropshell";
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"
};
std::string dropshell_base = homedir + "/.local/dropshell_files";
mConfig["server_definition_paths"] = {
dropshell_base + "/servers"
};
mConfig["template_upload_registry_url"] = "https://templates.dropshell.app";
mConfig["template_upload_registry_token"] = "SECRETTOKEN";
mConfig["template_local_paths"] = {
dropshell_base + "/local_templates"
};
mConfig["template_registry_URLs"] = {
"https://templates.dropshell.app"
};
mConfig["template_upload_token"] = "SECRETTOKEN";
}
config_file << mConfig.dump(4);
config_file.close();
if (create_aux_directories) {
std::vector<std::filesystem::path> paths = {
get_local_template_cache_path(),
get_local_backup_path(),
get_local_tempfiles_path()
};
for (auto & p : get_local_server_definition_paths())
paths.push_back(p);
std::vector<std::string> paths;
_append(paths, get_local_template_paths());
_append(paths, get_local_server_definition_paths());
for (auto & p : paths)
if (!std::filesystem::exists(p))
{
@ -97,6 +99,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;
}
@ -107,31 +114,20 @@ bool config::is_config_set() const
bool config::is_agent_installed()
{
return std::filesystem::exists(localpath::agent() + "/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"];
return std::filesystem::exists(localfile::bb64());
}
std::vector<std::string> config::get_template_registry_urls() {
nlohmann::json template_registry_urls = mConfig["template_registry_URLs"];
std::vector<std::string> urls;
for (auto &url : template_registry_urls) {
if (url.is_string() && !url.empty())
urls.push_back(url);
}
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"];
std::vector<std::string> paths;
@ -144,23 +140,40 @@ std::vector<std::string> config::get_template_local_paths()
std::vector<std::string> config::get_local_server_definition_paths() {
nlohmann::json server_definition_paths = mConfig["server_definition_paths"];
std::vector<std::string> paths;
for (auto &path : server_definition_paths) {
if (path.is_string() && !path.empty())
paths.push_back(path);
else
std::cerr << "Warning: Invalid server definition path: " << path << std::endl;
}
return paths;
}
std::string config::get_template_upload_registry_url() {
return mConfig["template_upload_registry_url"];
std::string config::get_server_create_path()
{
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() {
return mConfig["template_upload_registry_token"];
std::string config::get_template_create_path()
{
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

View File

@ -17,15 +17,14 @@ class config {
bool is_config_set() const;
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_local_paths();
std::vector<std::string> get_local_template_paths();
std::vector<std::string> get_local_server_definition_paths();
std::string get_template_upload_registry_url();
std::string get_template_upload_registry_token();
std::string get_server_create_path();
std::string get_template_create_path();
std::string get_template_upload_url();
std::string get_template_upload_token();
private:
nlohmann::json mConfig;

View File

@ -8,6 +8,7 @@
#include "autocomplete.hpp"
#include "utils/hash.hpp"
#include "command_registry.hpp"
#include "output.hpp"
#include <filesystem>
#include <iostream>
@ -49,33 +50,33 @@ int main(int argc, char* argv[]) {
return 0;
}
const CommandInfo* info = CommandRegistry::instance().find_command(ctx.command);
if (!info) {
std::cerr << "Unknown command: " << ctx.command << std::endl;
const CommandInfo* cmdinfo = CommandRegistry::instance().find_command(ctx.command);
if (!cmdinfo) {
error << "Unknown command: " << ctx.command << std::endl;
return 1;
}
if (info->requires_config && !gConfig().is_config_set()) {
std::cerr << "Valid dropshell configuration required for command: " << ctx.command << std::endl;
std::cerr << "Please run 'dropshell edit' to set up the dropshell configuration." << std::endl;
if (cmdinfo->requires_config && !gConfig().is_config_set()) {
error << "Valid dropshell configuration required for command: " << ctx.command << std::endl;
info << "Please run 'dropshell edit' to set up the dropshell configuration." << std::endl;
return 1;
}
if (info->requires_install && !gConfig().is_agent_installed()) {
std::cerr << "Dropshell agent not installed for command: " << ctx.command << std::endl;
std::cerr << "Please run 'dropshell install' to install the local dropshell agent." << std::endl;
if (cmdinfo->requires_install && !gConfig().is_agent_installed()) {
error << "Dropshell agent not installed for command: " << ctx.command << std::endl;
info << "Please run 'dropshell install' to install the local dropshell agent." << std::endl;
return 1;
}
int arg_count = ctx.args.size();
if (arg_count < info->min_args || (info->max_args != -1 && arg_count > info->max_args)) {
std::cerr << "Invalid number of arguments for command: " << ctx.command << std::endl;
std::cerr << "(" << ctx.args.size() << " args provided, " << ctx.command << " requires " << (info->min_args) << " to " << (info->max_args) << " args)" << std::endl;
std::cerr << "Usage: " << std::endl;
std::cerr << " ";
print_left_aligned(info->help_usage,30);
std::cout << info->help_description << std::endl;
if (arg_count < cmdinfo->min_args || (cmdinfo->max_args != -1 && arg_count > cmdinfo->max_args)) {
error << "Invalid number of arguments for command: " << ctx.command << std::endl;
debug << "(" << ctx.args.size() << " args provided, " << ctx.command << " requires " << (cmdinfo->min_args) << " to " << (cmdinfo->max_args) << " args)" << std::endl;
info << "Usage: " << std::endl;
info << " ";
info << left_align(cmdinfo->help_usage,30);
info << cmdinfo->help_description << std::endl;
return 1;
}
return info->handler(ctx);
return cmdinfo->handler(ctx);
}
catch (const std::exception& e) {
@ -115,13 +116,6 @@ bool getCLIServices(const std::string & arg2, const std::string & arg3,
return true;
}
void printversion() {
maketitle("DropShell version " + VERSION);
std::cout << "Release date: " << RELEASE_DATE << std::endl;
std::cout << "Author: " << AUTHOR << std::endl;
std::cout << "License: " << LICENSE << std::endl;
}
auto command_match = [](const std::string& cmd_list, int argc, char* argv[]) -> bool {
std::istringstream iss(cmd_list);
std::string cmd_item;

View File

@ -204,7 +204,7 @@ bool server_env_manager::run_remote_template_command(const std::string &service_
if (scommand->get_command_to_run().empty())
return false;
cMode mode = (command=="ssh") ? (cMode::Interactive) : cMode::Silent;
cMode mode = (command=="ssh") ? (cMode::Interactive) : (silent ? cMode::Silent : cMode::Defaults);
return execute_ssh_command(get_SSH_INFO(), scommand.value(), mode);
}
@ -218,8 +218,7 @@ bool server_env_manager::run_remote_template_command_and_capture_output(const st
for (const auto& [key, value] : extra_env_vars)
scommand->add_env_var(key, value);
cMode mode = cMode::CaptureOutput;
return execute_ssh_command(get_SSH_INFO(), scommand.value(), mode, &output);
return execute_ssh_command(get_SSH_INFO(), scommand.value(), cMode::Defaults, &output);
}

View File

@ -7,6 +7,7 @@
#include "config.hpp"
#include "templates.hpp"
#include "contrib/transwarp.hpp"
#include "utils/output.hpp"
#include <iostream>
#include <fstream>
@ -76,16 +77,16 @@ bool create_server(const std::string &server_name)
// 1. check if server name already exists
std::string server_existing_dir = localpath::server(server_name);
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;
}
// 2. create a new directory in the user config directory
auto lsdp = gConfig().get_local_server_definition_paths();
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;
}
std::string server_dir = lsdp[0] + "/" + server_name;
@ -93,20 +94,20 @@ bool create_server(const std::string &server_name)
// 3. create a template server.env file in the server directory
std::string user = getenv("USER");
std::string server_env_path = server_dir + "/server.env";
std::string server_env_path = server_dir + "/server.json";
std::ofstream server_env_file(server_env_path);
server_env_file << "SSH_HOST=" << server_name << std::endl;
server_env_file << "SSH_USER=" << user << std::endl;
server_env_file << "SSH_PORT=" << 22 << std::endl;
server_env_file << std::endl;
server_env_file << "DROPSHELL_DIR=/home/"+user+"/.dropshell" << std::endl;
server_env_file << "{" << std::endl;
server_env_file << " \"SSH_HOST\": \"" << server_name << "\"," << std::endl;
server_env_file << " \"SSH_USER\": \"" << user << "\"," << std::endl;
server_env_file << " \"SSH_PORT\": " << 22 << "," << std::endl;
server_env_file << " \"DROPSHELL_DIR\": \"" << "/home/"+user+"/.dropshell\"" << std::endl;
server_env_file << "}" << std::endl;
server_env_file.close();
std::cout << "Server created successfully: " << server_name << 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 << "2) test ssh is working: dropshell ssh " << server_name << std::endl;
std::cout << "3) install the server: dropshell install " << server_name << std::endl;
std::cout << "2) install the server: dropshell install " << server_name << std::endl;
std::cout << std::endl;
return true;
}

View File

@ -1,535 +1,529 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <chrono>
#include <iomanip>
#include <filesystem>
#include <unistd.h>
#include "utils/assert.hpp"
// #include <iostream>
// #include <fstream>
// #include <sstream>
// #include <cstdlib>
// #include <chrono>
// #include <iomanip>
// #include <filesystem>
// #include <unistd.h>
// #include "utils/assert.hpp"
#include "config.hpp"
#include "server_env_manager.hpp"
#include "templates.hpp"
#include "services.hpp"
#include "utils/directories.hpp"
#include "utils/utils.hpp"
#include "command_registry.hpp"
#include "shared_commands.hpp"
// #include "config.hpp"
// #include "server_env_manager.hpp"
// #include "templates.hpp"
// #include "services.hpp"
// #include "utils/directories.hpp"
// #include "utils/utils.hpp"
// #include "command_registry.hpp"
// #include "shared_commands.hpp"
namespace dropshell {
// namespace dropshell {
class service_runner {
public:
service_runner(const std::string& server_name, const std::string& service_name);
bool isValid() const { return mValid; }
// class service_runner {
// public:
// service_runner(const std::string& server_name, const std::string& service_name);
// bool isValid() const { return mValid; }
// run a command over ssh, using the credentials from server.env (via server_env.hpp)
// first check that the command corresponds to a valid .sh file in the service directory
// then run the command, passing the {service_name}.env file as an argument
// do a lot of checks, such as:
// checking that we can ssh to the server.
// checking whether the service directory exists on the server.
// checking that the command exists in the service directory.
// checking that the command is a valid .sh file.
// checking that the {service_name}.env file exists in the service directory.
bool run_command(const std::string& command, std::vector<std::string> additional_args={}, std::map<std::string, std::string> env_vars={});
// // run a command over ssh, using the credentials from server.env (via server_env.hpp)
// // first check that the command corresponds to a valid .sh file in the service directory
// // then run the command, passing the {service_name}.env file as an argument
// // do a lot of checks, such as:
// // checking that we can ssh to the server.
// // checking whether the service directory exists on the server.
// // checking that the command exists in the service directory.
// // checking that the command is a valid .sh file.
// // checking that the {service_name}.env file exists in the service directory.
// bool run_command(const std::string& command, std::vector<std::string> additional_args={}, std::map<std::string, std::string> env_vars={});
// check health of service. Silent.
// 1. run status.sh on the server
// 2. return the output of the status.sh script
// // check health of service. Silent.
// // 1. run status.sh on the server
// // 2. return the output of the status.sh script
//HealthStatus is_healthy();
// //HealthStatus is_healthy();
// std::string healthtick();
// std::string healthmark();
// // std::string healthtick();
// // std::string healthmark();
public:
// backup and restore
bool backup(bool silent=false);
bool restore(std::string backup_file, bool silent=false);
// public:
// // backup and restore
// bool backup(bool silent=false);
// bool restore(std::string backup_file, bool silent=false);
// nuke the service
bool nuke(bool silent=false); // nukes all data for this service on the remote server
bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles
// // nuke the service
// bool nuke(bool silent=false); // nukes all data for this service on the remote server
// bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles
// launch an interactive ssh session on a server or service
// replaces the current dropshell process with the ssh process
bool interactive_ssh_service();
// // launch an interactive ssh session on a server or service
// // replaces the current dropshell process with the ssh process
// bool interactive_ssh_service();
bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false);
bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false);
public:
// utility functions
static std::string get_latest_backup_file(const std::string& server, const std::string& service);
static bool interactive_ssh(const std::string & server_name, const std::string & command);
// static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
// bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false);
// bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false);
// public:
// // utility functions
// static std::string get_latest_backup_file(const std::string& server, const std::string& service);
// static bool interactive_ssh(const std::string & server_name, const std::string & command);
// // static std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name);
private:
std::string mServer;
server_env_manager mServerEnv;
LocalServiceInfo mServiceInfo;
std::string mService;
bool mValid;
// private:
// std::string mServer;
// server_env_manager mServerEnv;
// LocalServiceInfo mServiceInfo;
// std::string mService;
// bool mValid;
// Helper methods
public:
};
// // Helper methods
// public:
// };
} // namespace dropshell
// } // namespace dropshell
namespace fs = std::filesystem;
// namespace fs = std::filesystem;
namespace dropshell {
// namespace dropshell {
static const std::string magic_string = "-_-";
service_runner::service_runner(const std::string& server_name, const std::string& service_name) :
mServerEnv(server_name), mServer(server_name), mService(service_name), mValid(false)
{
if (server_name.empty() || service_name.empty())
return;
// service_runner::service_runner(const std::string& server_name, const std::string& service_name) :
// mServerEnv(server_name), mServer(server_name), mService(service_name), mValid(false)
// {
// if (server_name.empty() || service_name.empty())
// return;
// Initialize server environment
if (!mServerEnv.is_valid())
return;
// // Initialize server environment
// if (!mServerEnv.is_valid())
// return;
mServiceInfo = get_service_info(server_name, service_name);
if (mServiceInfo.service_name.empty())
return;
// mServiceInfo = get_service_info(server_name, service_name);
// if (mServiceInfo.service_name.empty())
// return;
mService = mServiceInfo.service_name;
// mService = mServiceInfo.service_name;
mValid = !mServiceInfo.local_template_path.empty();
}
// mValid = !mServiceInfo.local_template_path.empty();
// }
bool service_runner::nuke(bool silent)
{
maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);
// bool service_runner::nuke(bool silent)
// {
// maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);
if (!mServerEnv.is_valid()) return false; // should never hit this.
// if (!mServerEnv.is_valid()) return false; // should never hit this.
std::string remote_service_path = remotepath::service(mServer, mService);
std::cout << "Service " << mService << " successfully nuked from " << mServer << std::endl;
if (!silent) {
std::cout << "There's nothing left on the remote server." << std::endl;
std::cout << "You can remove the local files with:" << std::endl;
std::cout << " rm -rf " << localpath::service(mServer,mService) << std::endl;
}
return true;
}
bool service_runner::fullnuke()
{
if (!nuke(true))
{
std::cerr << "Warning: Nuke script failed, aborting fullnuke!" << std::endl;
return false;
}
std::string local_service_path = mServiceInfo.local_service_path;
if (local_service_path.empty() || !fs::exists(local_service_path)) {
std::cerr << "Error: Service directory not found: " << local_service_path << std::endl;
return false;
}
std::string rm_cmd = "rm -rf " + quote(local_service_path);
if (!execute_local_command(rm_cmd, nullptr, cMode::Silent)) {
std::cerr << "Failed to remove service directory" << std::endl;
return false;
}
return true;
}
// ------------------------------------------------------------------------------------------------
// Run a command on the service.
// ------------------------------------------------------------------------------------------------
bool service_runner::run_command(const std::string& command, std::vector<std::string> additional_args, std::map<std::string, std::string> env_vars) {
if (!mServerEnv.is_valid()) {
std::cerr << "Error: Server service not initialized" << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(mServiceInfo.template_name);
if (!tinfo.is_set()) {
std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl;
return false;
}
if (command == "fullnuke")
return fullnuke();
if (command == "nuke")
{
std::cout << "Nuking " << mService << " (" << mServiceInfo.template_name << ") on " << mServer << std::endl;
return nuke();
}
if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) {
std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
return true; // nothing to run.
}
// install doesn't require anything on the server yet.
// if (command == "install")
// return install_service(mServer, mService, false);
std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh";
// Check if service directory exists
if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
std::cerr << "Error: Service is not installed: " << mService << std::endl;
return false;
}
// Check if command script exists
if (!mServerEnv.check_remote_file_exists(script_path)) {
std::cerr << "Error: Remote command script not found: " << script_path << std::endl;
return false;
}
// Check if env file exists
if (!mServerEnv.check_remote_file_exists(remotefile::service_env(mServer, mService))) {
std::cerr << "Error: Service config file not found: " << remotefile::service_env(mServer, mService) << std::endl;
return false;
}
// if (command == "uninstall")
// return uninstall();
if (command == "ssh") {
interactive_ssh_service();
return true;
}
if (command == "restore") {
if (additional_args.size() < 1) {
std::cerr << "Error: restore requires a backup file:" << std::endl;
std::cerr << "dropshell restore <server> <service> <backup-file>" << std::endl;
return false;
}
return restore(additional_args[0], false);
}
if (command == "backup") {
return backup(false);
}
// Run the generic command
std::vector<std::string> args; // not passed through yet.
return mServerEnv.run_remote_template_command(mService, command, args, false, env_vars);
}
bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) {
std::string serverpath = localpath::server(server_name);
if (serverpath.empty()) {
std::cerr << "Error: Server not found: " << server_name << std::endl;
return false;
}
server_env_manager env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
return false;
}
sCommand scommand("", "bash",{});
return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
}
bool service_runner::interactive_ssh_service()
{
std::set<std::string> used_commands = get_used_commands(mServer, mService);
if (used_commands.find("ssh") == used_commands.end()) {
std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
return false;
}
std::vector<std::string> args; // not passed through yet.
return mServerEnv.run_remote_template_command(mService, "ssh", args, false, {});
}
bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent)
{
std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : "");
return execute_local_command(scp_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
bool service_runner::scp_file_from_remote(const std::string &remote_path, const std::string &local_path, bool silent)
{
std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : "");
return execute_local_command(scp_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
bool service_runner::restore(std::string backup_file, bool silent)
{
if (backup_file.empty()) {
std::cerr << "Error: not enough arguments. dropshell restore <server> <service> <backup-file>" << std::endl;
return false;
}
std::string local_backups_dir = gConfig().get_local_backup_path();
if (backup_file == "latest") {
// get the latest backup file from the server
backup_file = get_latest_backup_file(mServer, mService);
}
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string();
if (! std::filesystem::exists(local_backup_file_path)) {
std::cerr << "Error: Backup file not found at " << local_backup_file_path << std::endl;
return false;
}
// split the backup filename into parts based on the magic string
std::vector<std::string> parts = dropshell::split(backup_file, magic_string);
if (parts.size() != 4) {
std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
return false;
}
std::string backup_server_name = parts[0];
std::string backup_template_name = parts[1];
std::string backup_service_name = parts[2];
std::string backup_datetime = parts[3];
if (backup_template_name != mServiceInfo.template_name) {
std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl;
return false;
}
std::string nicedate = std::string(backup_datetime).substr(0, 10);
std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<<backup_server_name<<", onto "<<mServer<<"/"<<mService<<std::endl;
std::cout << std::endl;
std::cout << "*** ALL DATA FOR "<<mServer<<"/"<<mService<<" WILL BE OVERWRITTEN! ***"<<std::endl;
// run the restore script
std::cout << "OK, here goes..." << std::endl;
{ // backup existing service
maketitle("1) Backing up old service... ");
if (!backup(true)) // silent=true
{
std::cerr << std::endl;
std::cerr << "Error: Backup failed, restore aborted." << std::endl;
std::cerr << "You can try using dropshell install "<<mServer<<" "<<mService<<" to install the service afresh." << std::endl;
std::cerr << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl;
return false;
}
std::cout << "Backup complete." << std::endl;
}
{ // uninstall service, then nuke it.
maketitle("2) Uninstalling old service...");
// if (!uninstall(true))
// return false;
maketitle("3) Nuking old service...");
// std::string remote_service_path = remotepath::service(mServer, mService);
// info << "Service " << mService << " successfully nuked from " << mServer << std::endl;
// if (!silent) {
// info << "There's nothing left on the remote server." << std::endl;
// info << "You can remove the local files with:" << std::endl;
// info << " rm -rf " << localpath::service(mServer,mService) << std::endl;
// }
// return true;
// }
// bool service_runner::fullnuke()
// {
// if (!nuke(true))
// return false;
}
{ // restore service from backup
maketitle("4) Restoring service data from backup...");
std::string remote_backups_dir = remotepath::backups(mServer);
std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// Copy backup file from local to server
if (!scp_file_to_remote(local_backup_file_path, remote_backup_file_path, silent)) {
std::cerr << "Failed to copy backup file from local to server" << std::endl;
return false;
}
shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
mServerEnv.run_remote_template_command(mService, "restore", {}, silent, {{"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
// { // installing fresh service
// maketitle("5) Non-destructive install of fresh service...");
// if (!install_service(mServer, mService, true))
// {
// warning << "Nuke script failed, aborting." << std::endl;
// return false;
// }
bool healthy = false;
{// healthcheck the service
maketitle("6) Healthchecking service...");
std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m";
healthy= (mServerEnv.run_remote_template_command(mService, "status", {}, silent, {}));
if (!silent)
std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
}
// std::string local_service_path = mServiceInfo.local_service_path;
// if (local_service_path.empty() || !fs::exists(local_service_path)) {
// error << "Service directory not found: " << local_service_path << std::endl;
// return false;
// }
return healthy;
}
// std::string rm_cmd = "rm -rf " + quote(local_service_path);
// if (!execute_local_command("", rm_cmd, {}, nullptr, cMode::Silent)) {
// error << "Failed to remove service directory" << std::endl;
// return false;
// }
// return true;
// }
bool name_breaks_backups(std::string name)
{
// if name contains -_-, return true
return name.find("-_-") != std::string::npos;
}
// // ------------------------------------------------------------------------------------------------
// // Run a command on the service.
// // ------------------------------------------------------------------------------------------------
// bool service_runner::run_command(const std::string& command, std::vector<std::string> additional_args, std::map<std::string, std::string> env_vars) {
// if (!mServerEnv.is_valid()) {
// std::cerr << "Error: Server service not initialized" << std::endl;
// return false;
// }
// template_info tinfo = gTemplateManager().get_template_info(mServiceInfo.template_name);
// if (!tinfo.is_set()) {
// std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl;
// return false;
// }
// backup the service over ssh, using the credentials from server.env (via server_env.hpp)
// 1. run backup.sh on the server
// 2. create a backup file with format server-service-datetime.tgz
// 3. store it in the server's DROPSHELL_DIR/backups folder
// 4. copy it to the local user_dir/backups folder
// if (command == "fullnuke")
// return fullnuke();
// ------------------------------------------------------------------------------------------------
// Backup the service.
// ------------------------------------------------------------------------------------------------
bool service_runner::backup(bool silent) {
auto service_info = get_service_info(mServer, mService);
if (service_info.local_service_path.empty()) {
std::cerr << "Error: Service not found" << std::endl;
return 1;
}
// if (command == "nuke")
// {
// std::cout << "Nuking " << mService << " (" << mServiceInfo.template_name << ") on " << mServer << std::endl;
// return nuke();
// }
const std::string command = "backup";
// if (!gTemplateManager().template_command_exists(mServiceInfo.template_name, command)) {
// std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
// return true; // nothing to run.
// }
if (!gTemplateManager().template_command_exists(service_info.template_name, command)) {
std::cout << "No backup script for " << service_info.template_name << std::endl;
return true; // nothing to back up.
}
// // install doesn't require anything on the server yet.
// // if (command == "install")
// // return install_service(mServer, mService, false);
// Check if basic installed stuff is in place.
std::string remote_service_template_path = remotepath::service_template(mServer, mService);
std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
std::string remote_service_config_path = remotepath::service_config(mServer, mService);
if (!mServerEnv.check_remote_items_exist({
remotepath::service(mServer, mService),
remote_command_script_file,
remotefile::service_env(mServer, mService)})
)
{
std::cerr << "Error: Required service directories not found on remote server" << std::endl;
std::cerr << "Is the service installed?" << std::endl;
return false;
}
// std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh";
// Create backups directory on server if it doesn't exist
std::string remote_backups_dir = remotepath::backups(mServer);
if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Silent)) {
std::cerr << "Failed to create backups directory on server" << std::endl;
return false;
}
// // Check if service directory exists
// if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
// std::cerr << "Error: Service is not installed: " << mService << std::endl;
// return false;
// }
// Create backups directory locally if it doesn't exist
std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty()) {
std::cerr << "Error: Local backups directory not found" << std::endl;
std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
return false;
}
if (!std::filesystem::exists(local_backups_dir))
std::filesystem::create_directories(local_backups_dir);
// // Check if command script exists
// if (!mServerEnv.check_remote_file_exists(script_path)) {
// std::cerr << "Error: Remote command script not found: " << script_path << std::endl;
// return false;
// }
// 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");
// // Check if env file exists
// if (!mServerEnv.check_remote_file_exists(remotefile::service_env(mServer, mService))) {
// std::cerr << "Error: Service config file not found: " << remotefile::service_env(mServer, mService) << std::endl;
// return false;
// }
if (name_breaks_backups(mServer)) {std::cerr << "Error: Server name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;}
if (name_breaks_backups(mService)) {std::cerr << "Error: Service name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;}
if (name_breaks_backups(service_info.template_name)) {std::cerr << "Error: Service template name contains invalid character sequence ( -_- ) that would break backup naming scheme" << std::endl; return 1;}
// // if (command == "uninstall")
// // return uninstall();
// Construct backup filename
std::string backup_filename = mServer + magic_string + service_info.template_name + magic_string + mService + magic_string + datetime.str() + ".tgz";
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();
// if (command == "ssh") {
// interactive_ssh_service();
// return true;
// }
// if (command == "restore") {
// if (additional_args.size() < 1) {
// std::cerr << "Error: restore requires a backup file:" << std::endl;
// std::cerr << "dropshell restore <server> <service> <backup-file>" << std::endl;
// return false;
// }
// return restore(additional_args[0], false);
// }
// if (command == "backup") {
// return backup(false);
// }
// 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 the generic command
// std::vector<std::string> args; // not passed through yet.
// return mServerEnv.run_remote_template_command(mService, command, args, false, env_vars);
// }
{ // Run backup script
shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
if (!mServerEnv.run_remote_template_command(mService, command, {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) {
std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl;
return false;
}
// Copy backup file from server to local
if (!scp_file_from_remote(remote_backup_file_path, local_backup_file_path, silent)) {
std::cerr << "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
if (!silent) {
std::cout << "Backup created successfully. Restore with:"<<std::endl;
std::cout << " dropshell restore " << mServer << " " << mService << " " << backup_filename << std::endl;
}
return true;
}
// bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) {
// std::string serverpath = localpath::server(server_name);
// if (serverpath.empty()) {
// std::cerr << "Error: Server not found: " << server_name << std::endl;
// return false;
// }
// Helper function to get the latest backup file for a given server and service
std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) {
std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return "";
}
// server_env_manager env(server_name);
// if (!env.is_valid()) {
// std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
// return false;
// }
// sCommand scommand("", "bash",{});
// return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
// }
// Get the template name for this service
LocalServiceInfo info = get_service_info(server, service);
if (info.template_name.empty()) {
std::cerr << "Error: Could not determine template name for service: " << service << std::endl;
return "";
}
// Build the expected prefix for backup files
std::string prefix = server + magic_string + info.template_name + magic_string + service + magic_string;
std::string latest_file;
std::string latest_datetime;
std::cout << "Looking for backup files in " << local_backups_dir << std::endl;
// bool service_runner::interactive_ssh_service()
// {
// std::set<std::string> used_commands = get_used_commands(mServer, mService);
// if (used_commands.find("ssh") == used_commands.end()) {
// std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
// return false;
// }
for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
if (!entry.is_regular_file()) continue;
std::string filename = entry.path().filename().string();
if (filename.rfind(prefix, 0) == 0) { // starts with prefix
// Extract the datetime part
size_t dt_start = prefix.size();
size_t dt_end = filename.find(".tgz", dt_start);
if (dt_end == std::string::npos) continue;
std::string datetime = filename.substr(dt_start, dt_end - dt_start);
std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl;
if (datetime > latest_datetime) {
latest_datetime = datetime;
latest_file = filename;
}
}
}
// std::vector<std::string> args; // not passed through yet.
// return mServerEnv.run_remote_template_command(mService, "ssh", args, false, {});
// }
if (latest_file.empty()) {
std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl;
}
// bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent)
// {
// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + quote(local_path) + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + (silent ? " > /dev/null 2>&1" : "");
// return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
// }
std::cout << "Latest backup file: " << latest_file << std::endl;
return latest_file;
}
// bool service_runner::scp_file_from_remote(const std::string &remote_path, const std::string &local_path, bool silent)
// {
// std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + quote(remote_path) + " " + quote(local_path) + (silent ? " > /dev/null 2>&1" : "");
// return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
// }
} // namespace dropshell
// bool service_runner::restore(std::string backup_file, bool silent)
// {
// if (backup_file.empty()) {
// std::cerr << "Error: not enough arguments. dropshell restore <server> <service> <backup-file>" << std::endl;
// return false;
// }
// std::string local_backups_dir = gConfig().get_local_backup_path();
// if (backup_file == "latest") {
// // get the latest backup file from the server
// backup_file = get_latest_backup_file(mServer, mService);
// }
// std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string();
// if (! std::filesystem::exists(local_backup_file_path)) {
// std::cerr << "Error: Backup file not found at " << local_backup_file_path << std::endl;
// return false;
// }
// // split the backup filename into parts based on the magic string
// std::vector<std::string> parts = dropshell::split(backup_file, "-_-");
// if (parts.size() != 4) {
// std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
// return false;
// }
// std::string backup_server_name = parts[0];
// std::string backup_template_name = parts[1];
// std::string backup_service_name = parts[2];
// std::string backup_datetime = parts[3];
// if (backup_template_name != mServiceInfo.template_name) {
// std::cerr << "Error: Backup template does not match service template. Can't restore." << std::endl;
// return false;
// }
// std::string nicedate = std::string(backup_datetime).substr(0, 10);
// std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<<backup_server_name<<", onto "<<mServer<<"/"<<mService<<std::endl;
// std::cout << std::endl;
// std::cout << "*** ALL DATA FOR "<<mServer<<"/"<<mService<<" WILL BE OVERWRITTEN! ***"<<std::endl;
// // run the restore script
// std::cout << "OK, here goes..." << std::endl;
// { // backup existing service
// maketitle("1) Backing up old service... ");
// if (!backup(true)) // silent=true
// {
// std::cerr << std::endl;
// std::cerr << "Error: Backup failed, restore aborted." << std::endl;
// std::cerr << "You can try using dropshell install "<<mServer<<" "<<mService<<" to install the service afresh." << std::endl;
// std::cerr << "Otherwise, stop the service, create and initialise a new one, then restore to that." << std::endl;
// return false;
// }
// std::cout << "Backup complete." << std::endl;
// }
// { // uninstall service, then nuke it.
// maketitle("2) Uninstalling old service...");
// // if (!uninstall(true))
// // return false;
// maketitle("3) Nuking old service...");
// // if (!nuke(true))
// // return false;
// }
// { // restore service from backup
// maketitle("4) Restoring service data from backup...");
// std::string remote_backups_dir = remotepath::backups(mServer);
// std::string remote_backup_file_path = remote_backups_dir + "/" + backup_file;
// // Copy backup file from local to server
// if (!scp_file_to_remote(local_backup_file_path, remote_backup_file_path, silent)) {
// std::cerr << "Failed to copy backup file from local to server" << std::endl;
// return false;
// }
// shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
// mServerEnv.run_remote_template_command(mService, "restore", {}, silent, {{"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
// // { // installing fresh service
// // maketitle("5) Non-destructive install of fresh service...");
// // if (!install_service(mServer, mService, true))
// // return false;
// // }
// bool healthy = false;
// {// healthcheck the service
// maketitle("6) Healthchecking service...");
// std::string green_tick = "\033[32m✓\033[0m";
// std::string red_cross = "\033[31m✗\033[0m";
// healthy= (mServerEnv.run_remote_template_command(mService, "status", {}, silent, {}));
// if (!silent)
// std::cout << (healthy ? green_tick : red_cross) << " Service is " << (healthy ? "healthy" : "NOT healthy") << std::endl;
// }
// return healthy;
// }
// // backup the service over ssh, using the credentials from server.env (via server_env.hpp)
// // 1. run backup.sh on the server
// // 2. create a backup file with format server-service-datetime.tgz
// // 3. store it in the server's DROPSHELL_DIR/backups folder
// // 4. copy it to the local user_dir/backups folder
// // ------------------------------------------------------------------------------------------------
// // Backup the service.
// // ------------------------------------------------------------------------------------------------
// bool service_runner::backup(bool silent) {
// auto service_info = get_service_info(mServer, mService);
// if (service_info.local_service_path.empty()) {
// std::cerr << "Error: Service not found" << std::endl;
// return 1;
// }
// const std::string command = "backup";
// if (!gTemplateManager().template_command_exists(service_info.template_name, command)) {
// std::cout << "No backup script for " << service_info.template_name << std::endl;
// return true; // nothing to back up.
// }
// // Check if basic installed stuff is in place.
// std::string remote_service_template_path = remotepath::service_template(mServer, mService);
// std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
// std::string remote_service_config_path = remotepath::service_config(mServer, mService);
// if (!mServerEnv.check_remote_items_exist({
// remotepath::service(mServer, mService),
// remote_command_script_file,
// remotefile::service_env(mServer, mService)})
// )
// {
// std::cerr << "Error: Required service directories not found on remote server" << std::endl;
// std::cerr << "Is the service installed?" << std::endl;
// return false;
// }
// // Create backups directory on server if it doesn't exist
// std::string remote_backups_dir = remotepath::backups(mServer);
// if (!silent) std::cout << "Remote backups directory on "<< mServer <<": " << remote_backups_dir << std::endl;
// std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
// if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("",mkdir_cmd, {}), cMode::Silent)) {
// std::cerr << "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 = gConfig().get_local_backup_path();
// if (local_backups_dir.empty()) {
// std::cerr << "Error: Local backups directory not found" << std::endl;
// std::cerr << "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
// 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");
// // Construct backup filename
// shared_commands::cBackupFileName backup_filename_construction(mServer, mService, service_info.template_name);
// if (!backup_filename_construction.is_valid()) {
// std::cerr << "Invalid backup filename" << std::endl;
// return false;
// }
// 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("-_-", local_backup_file_path), "Invalid backup filename");
// { // Run backup script
// shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
// if (!mServerEnv.run_remote_template_command(mService, command, {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) {
// std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl;
// return false;
// }
// // Copy backup file from server to local
// if (!scp_file_from_remote(remote_backup_file_path, local_backup_file_path, silent)) {
// std::cerr << "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
// if (!silent) {
// std::cout << "Backup created successfully. Restore with:"<<std::endl;
// std::cout << " dropshell restore " << mServer << " " << mService << " " << backup_filename << std::endl;
// }
// return true;
// }
// // Helper function to get the latest backup file for a given server and service
// std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) {
// std::string local_backups_dir = gConfig().get_local_backup_path();
// if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
// std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
// return "";
// }
// // Get the template name for this service
// LocalServiceInfo info = get_service_info(server, service);
// if (info.template_name.empty()) {
// std::cerr << "Error: Could not determine template name for service: " << service << std::endl;
// return "";
// }
// // Build the expected prefix for backup files
// std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-";
// std::string latest_file;
// std::string latest_datetime;
// std::cout << "Looking for backup files in " << local_backups_dir << std::endl;
// for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
// if (!entry.is_regular_file()) continue;
// std::string filename = entry.path().filename().string();
// if (filename.rfind(prefix, 0) == 0) { // starts with prefix
// // Extract the datetime part
// size_t dt_start = prefix.size();
// size_t dt_end = filename.find(".tgz", dt_start);
// if (dt_end == std::string::npos) continue;
// std::string datetime = filename.substr(dt_start, dt_end - dt_start);
// std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl;
// if (datetime > latest_datetime) {
// latest_datetime = datetime;
// latest_file = filename;
// }
// }
// }
// if (latest_file.empty()) {
// std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl;
// }
// std::cout << "Latest backup file: " << latest_file << std::endl;
// return latest_file;
// }
// } // namespace dropshell

View File

@ -47,7 +47,7 @@ std::vector<LocalServiceInfo> get_server_services_info(const std::string& server
if (!service.local_service_path.empty())
services.push_back(service);
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
}
@ -137,7 +137,7 @@ std::set<std::string> list_backups(const std::string &server_name, const std::st
return backups;
}
std::string backups_dir = gConfig().get_local_backup_path();
std::string backups_dir = localpath::backups();
if (backups_dir.empty())
return backups;
@ -176,6 +176,7 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
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
auto load_env_file = [&all_env_vars](const std::string& file) {
@ -187,7 +188,7 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
all_env_vars.merge(env_vars);
}
else
std::cout << "Warning: Expected environment file not found: " << file << std::endl;
warning << "Expected environment file not found: " << file << std::endl;
};
// Load environment files
@ -197,11 +198,10 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
// determine template name.
auto it = all_env_vars.find("TEMPLATE");
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;
std::cerr << "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;
std::cerr << " " << localpath::service(server_name, service_name) << std::endl << std::endl;
error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
info << "The TEMPLATE variable is required to determine the template name." << std::endl;
info << "Please check the service.env file and the .template_info.env file in:" << std::endl;
info << " " << localpath::service(server_name, service_name) << std::endl << std::endl;
return false;
}
template_info tinfo = gTemplateManager().get_template_info(it->second);

View File

@ -185,7 +185,7 @@
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()) {
std::cerr << "Error: No local template paths found" << std::endl;
std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl;
@ -252,7 +252,7 @@
ASSERT(mSources.empty(), "Template manager already loaded (sources are not empty).");
ASSERT(gConfig().is_config_set(), "Config not set.");
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())
return;
for (const auto& path : local_template_paths)

View File

@ -19,11 +19,11 @@ class template_info {
template_info() : mIsSet(false) {}
template_info(const std::string& template_name, const std::string& location_id, const std::filesystem::path& local_template_path);
virtual ~template_info() {}
bool is_set() { return mIsSet; }
std::string name() { return mTemplateName; }
std::string locationID() { return mLocationID; }
std::filesystem::path local_template_path() { return mTemplateLocalPath; }
bool template_valid() { return mTemplateValid; }
bool is_set() const { return mIsSet; }
std::string name() const { return mTemplateName; }
std::string locationID() const { return mLocationID; }
std::filesystem::path local_template_path() const { return mTemplateLocalPath; }
bool template_valid() const { return mTemplateValid; }
private:
std::string mTemplateName;
std::string mLocationID;

View File

@ -1,10 +1,11 @@
#ifndef ASSERT_HPP
#define ASSERT_HPP
#include "output.hpp"
#define ASSERT(condition, message) \
if (!(condition)) { \
std::cerr << "Assertion failed: " << message << std::endl; \
dropshell::error << "Assertion failed: " << message << std::endl; \
std::exit(1); \
}

View File

@ -1,10 +1,13 @@
#include "directories.hpp"
#include "config.hpp"
#include "server_env_manager.hpp"
#include "output.hpp"
#include <iostream>
#include <string>
#include <filesystem>
namespace fs = std::filesystem;
namespace dropshell {
@ -38,6 +41,16 @@ namespace localfile {
return (servicepath.empty() ? "" : (fs::path(servicepath) / ".template_info.env").string());
}
std::string template_example()
{
return localpath::agent_local() + "/template_example";
}
std::string bb64()
{
return localpath::agent_local() + "/bb64";
}
} // namespace localfile
@ -59,16 +72,17 @@ namespace localpath {
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()) ? "" :
(template_cache_path+"/remote_versions/"+service_name+".json"));
}
std::string agent(){
return current_user_home() + "/.local/dropshell_agent";
}
std::string files_for_remote_agent()
std::string agent_local()
{
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()
{
@ -78,9 +92,53 @@ namespace localpath {
std::filesystem::path homedir_path(homedir);
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();
}
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
//------------------------------------------------------------------------------------------------

View File

@ -14,25 +14,18 @@ namespace dropshell {
// ~/.config/dropshell/dropshell.json
// ~/.local/dropshell_agent
// |-- agent-local
// |-- agent-install.sh
// |-- bb64 (only used locally, as it's for the local machine's architecture!)
// |-- files_for_remote_agent
// |-- (other agent files, including _allservicesstatus.sh)
// |-- template_example
// |-- agent-remote
// |-- (remote agent files, including _allservicesstatus.sh)
// server_definition_path
// |-- <server_name>
// |-- server.json
// |-- services
// |-- <service_name>
// |-- service.env
// |-- .template_info.env
// |-- (...other config files for specific server&service...)
// backup path
// ~/.local/dropshell_files
// |-- backups
// |-- katie-_-squashkiwi-_-squashkiwi-test-_-2025-04-28_21-23-59.tgz
// temp files path
// template cache path
// |-- temp_files
// |-- template_cache
// |-- templates
// | |-- <template_name>.json
// | |-- <template_name>
@ -45,12 +38,25 @@ namespace dropshell {
// |-- remote_versions
// | |-- server_name-service_name.json
// server_definition_path
// |-- <server_name>
// |-- server.json
// |-- services
// |-- <service_name>
// |-- service.env
// |-- .template_info.env
// |-- (...other config files for specific server&service...)
namespace localfile {
// ~/.config/dropshell/dropshell.json
std::string dropshell_json();
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 template_info_env(const std::string &server_name, const std::string &service_name);
std::string template_example();
std::string bb64();
} // namespace localfile
namespace localpath {
@ -59,9 +65,16 @@ namespace dropshell {
std::string remote_versions(const std::string &server_name, const std::string &service_name);
std::string agent();
std::string files_for_remote_agent();
std::string agent_local();
std::string agent_remote();
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

View File

@ -13,6 +13,7 @@
#include "utils/b64ed.hpp"
#include "config.hpp"
#include "utils/directories.hpp"
#include "utils/output.hpp"
namespace dropshell
{
@ -21,11 +22,14 @@ namespace dropshell
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
}
// ----------------------------------------------------------------------------------------------------------
// execute_local_command_interactive
// ----------------------------------------------------------------------------------------------------------
bool execute_local_command_interactive(const sCommand &command)
{
if (command.get_command_to_run().empty())
return false;
std::string full_command = command.construct_cmd(localpath::agent()); // Get the command string
std::string full_command = command.construct_cmd(localfile::bb64()); // Get the command string
pid_t pid = fork();
@ -51,30 +55,62 @@ namespace dropshell
}
}
bool execute_local_command_and_capture_output(const sCommand &command, std::string *output)
// ----------------------------------------------------------------------------------------------------------
// execute_local_command
// ----------------------------------------------------------------------------------------------------------
class fancypinter
{
ASSERT(output != nullptr, "Output string must be provided");
if (command.get_command_to_run().empty())
return false;
std::string full_cmd = command.construct_cmd(localpath::agent()) + " 2>&1";
FILE *pipe = popen(full_cmd.c_str(), "r");
if (!pipe)
public:
fancypinter(sColour startColour) : startColour_(startColour), currentColour_(startColour) {}
void print_chunk(std::string chunk)
{
return false;
if (chunk.empty())
return;
if (newline_)
{
// sniff the mode... if the string starts with warning or warning: then set mode to WARNING. etc.
if (chunk.find("warning") == 0)
currentColour_ = sColour::WARNING;
else if (chunk.find("error") == 0)
currentColour_ = sColour::ERROR;
else if (chunk.find("debug") == 0)
currentColour_ = sColour::DEBUG;
else if (chunk.find("info") == 0)
currentColour_ = sColour::INFO;
else
currentColour_ = startColour_;
}
char buffer[128];
while (fgets(buffer, sizeof(buffer), pipe) != nullptr)
{
(*output) += buffer;
}
int ret = pclose(pipe);
return EXITSTATUSCHECK(ret);
colourstream(currentColour_) << chunk;
newline_ = (chunk[chunk.size() - 1] == '\n');
}
bool execute_local_command(std::string command, std::string *output, cMode mode)
void print(const std::string &buffer)
{
return execute_local_command("", command, {}, output, mode);
size_t start = 0;
while (start < buffer.size())
{
size_t newline_pos = buffer.find('\n', start);
if (newline_pos == std::string::npos)
{
if (start < buffer.size())
{
print_chunk(buffer.substr(start));
}
break;
}
print_chunk(buffer.substr(start, newline_pos - start + 1)); // include the newline
start = newline_pos + 1;
}
}
private:
bool newline_ = true;
sColour startColour_;
sColour currentColour_;
};
bool execute_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> &env_vars, std::string *output, cMode mode)
{
@ -82,51 +118,64 @@ namespace dropshell
if (hasFlag(mode, cMode::Interactive))
{
ASSERT(!hasFlag(mode, cMode::CaptureOutput), "Interactive mode and capture output mode cannot be used together");
ASSERT(output == nullptr, "Interactive mode and an output string cannot be used together");
return execute_local_command_interactive(command);
}
if (hasFlag(mode, cMode::CaptureOutput))
{
ASSERT(output != nullptr, "Capture output mode requires an output string to be provided");
ASSERT(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
return execute_local_command_and_capture_output(command, output);
}
if (command.get_command_to_run().empty())
return false;
bool silent = hasFlag(mode, cMode::Silent);
std::string full_cmd = command.construct_cmd(localpath::agent()) + " 2>&1" + (silent ? " > /dev/null" : "");
int ret = system(full_cmd.c_str());
bool ok = EXITSTATUSCHECK(ret);
if (!ok && !silent)
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");
if (!pipe)
{
std::cerr << "Error: Failed to execute command: " << std::endl;
std::cerr << full_cmd << std::endl;
return false;
}
return ok;
char buffer[128];
fancypinter fancyprint(sColour::DEBUG);
while (fgets(buffer, sizeof(buffer), pipe) != nullptr)
{
if (output != nullptr)
(*output) += buffer;
if (!silent)
fancyprint.print(buffer);
}
int ret = pclose(pipe);
return EXITSTATUSCHECK(ret);
}
// ----------------------------------------------------------------------------------------------------------
// execute_ssh_command
// ----------------------------------------------------------------------------------------------------------
bool execute_ssh_command(const sSSHInfo &ssh_info, const sCommand &remote_command, cMode mode, std::string *output)
{
if (remote_command.get_command_to_run().empty())
return false;
ASSERT(!(hasFlag(mode, cMode::CaptureOutput) && output == nullptr), "Capture output mode must be used with an output string");
std::stringstream ssh_cmd;
ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "")
<< ssh_info.user << "@" << ssh_info.host;
std::string remote_agent_path = remotepath::agent(ssh_info.server_ID);
std::string remote_bb64_path;
if (!hasFlag(mode, cMode::NoBB64))
remote_bb64_path = remotepath::agent(ssh_info.server_ID) + "/bb64";
bool rval = execute_local_command(
"", // directory to run in
ssh_cmd.str() + " " + remote_command.construct_cmd(remote_agent_path), // local command to run
"", // local directory to run in
ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path), // local command to run
{}, // environment variables
output, // output string
mode // mode
@ -134,26 +183,28 @@ namespace dropshell
if (!rval && !hasFlag(mode, cMode::Silent))
{
std::cerr << std::endl
<< std::endl;
std::cerr << "Error: Failed to execute ssh command:" << std::endl;
std::cerr << "\033[90m" << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_agent_path) << "\033[0m" << std::endl;
std::cerr << std::endl
<< std::endl;
error << "Error: Failed to execute ssh command:" << std::endl;
debug << ssh_cmd.str() + " " + remote_command.construct_cmd(remote_bb64_path) << std::endl;
}
return rval;
}
std::string sCommand::makesafecmd(std::string agent_path, const std::string &command) const
// ----------------------------------------------------------------------------------------------------------
// makesafecmd
// ----------------------------------------------------------------------------------------------------------
std::string sCommand::makesafecmd(std::string bb64path, const std::string &command) const
{
if (command.empty())
return "";
std::string encoded = base64_encode(dequote(trim(command)));
std::string commandstr = agent_path + "/bb64 " + encoded;
std::string commandstr = bb64path + " " + encoded;
return commandstr;
}
std::string sCommand::construct_cmd(std::string agent_path) const
// ----------------------------------------------------------------------------------------------------------
// construct_cmd
// ----------------------------------------------------------------------------------------------------------
std::string sCommand::construct_cmd(std::string bb64path) const
{
if (mCmd.empty())
return "";
@ -161,6 +212,8 @@ namespace dropshell
// need to construct to change directory and set environment variables
std::string cmdstr;
if (!bb64path.empty())
{
if (!mDir.empty())
cmdstr += "cd " + quote(mDir) + " && ";
@ -170,8 +223,16 @@ namespace dropshell
cmdstr += mCmd;
if (!agent_path.empty())
cmdstr = makesafecmd(agent_path, cmdstr);
cmdstr = makesafecmd(bb64path, cmdstr);
}
else
{ // raw! bootstrapping only.
ASSERT(mVars.empty(), "Bootstrapping command must not have environment variables");
if (!mDir.empty())
cmdstr += mDir + "/" + mCmd;
else
cmdstr += mCmd;
}
return cmdstr;
}

View File

@ -13,7 +13,7 @@ enum class cMode {
Defaults = 0,
Interactive = 1,
Silent = 2,
CaptureOutput = 4
NoBB64 = 4
};
inline cMode operator&(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) & static_cast<int>(rhs));}
@ -31,7 +31,6 @@ typedef struct sSSHInfo {
std::string server_ID; // dropshell name for server.
} sSSHInfo;
bool execute_local_command(std::string command, 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);
@ -53,10 +52,10 @@ class sCommand {
bool empty() const { return mCmd.empty(); }
std::string construct_cmd(std::string agent_path) const;
std::string construct_cmd(std::string bb64path) const;
private:
std::string makesafecmd(std::string agent_path, const std::string& command) const;
std::string makesafecmd(std::string bb64path, const std::string& command) const;
private:
std::string mDir;

133
source/src/utils/output.cpp Normal file
View File

@ -0,0 +1,133 @@
#include "output.hpp"
#include <iostream>
#include <mutex>
namespace dropshell
{
// Mutex to synchronize output
std::mutex output_mutex;
// ANSI colour codes
constexpr const char *GREY = "\033[90m";
constexpr const char *RESET = "\033[0m";
constexpr const char *DEBUG_COLOUR = "\033[36m"; // Cyan
// constexpr const char *INFO_COLOUR = "\033[32m"; // Green
constexpr const char *INFO_COLOUR = "\033[37m"; // White
constexpr const char *WARNING_COLOUR = "\033[33m"; // Yellow
constexpr const char *ERROR_COLOUR = "\033[31m"; // Red
// Tag and colour for each stream
struct StreamInfo
{
const char *tag;
const char *colour;
};
const StreamInfo stream_infos[] = {
{"[DBG]", DEBUG_COLOUR},
{"[INF]", INFO_COLOUR},
{"[WRN]", WARNING_COLOUR},
{"[ERR]", ERROR_COLOUR}};
// Custom streambuf to prefix and colour each line
class PrefixStreambuf : public std::streambuf
{
public:
PrefixStreambuf(std::ostream &dest, const char *tag, const char *colour)
: dest_(dest), tag_(tag), colour_(colour), at_line_start_(true) {}
protected:
int overflow(int c) override
{
std::lock_guard<std::mutex> lock(output_mutex);
if (c == EOF)
return !EOF;
if (at_line_start_) // && c != '\n')
{
dest_ << GREY << tag_ << RESET << ' ' << colour_;
at_line_start_ = false;
}
dest_.put(static_cast<char>(c));
if (c == '\n')
{
dest_ << RESET;
at_line_start_ = true;
}
return c;
}
private:
std::ostream &dest_;
const char *tag_;
const char *colour_;
bool at_line_start_;
};
PrefixStreambuf debug_buf(std::clog, stream_infos[0].tag, stream_infos[0].colour);
PrefixStreambuf info_buf(std::clog, stream_infos[1].tag, stream_infos[1].colour);
PrefixStreambuf warning_buf(std::clog, stream_infos[2].tag, stream_infos[2].colour);
PrefixStreambuf error_buf(std::cerr, stream_infos[3].tag, stream_infos[3].colour);
std::ostream debug_stream(&debug_buf);
std::ostream info_stream(&info_buf);
std::ostream warning_stream(&warning_buf);
std::ostream error_stream(&error_buf);
std::ostream &debug = debug_stream;
std::ostream &info = info_stream;
std::ostream &warning = warning_stream;
std::ostream &error = error_stream;
std::ostream &rawout = std::cout;
std::ostream &rawerr = std::cerr;
std::ostream &colourstream(sColour colour)
{
switch (colour)
{
case sColour::DEBUG:
return debug_stream;
case sColour::INFO:
return info_stream;
case sColour::WARNING:
return warning_stream;
case sColour::ERROR:
return error_stream;
default:
return info_stream;
}
}
void SetColour(sColour colour, std::ostream &os)
{
switch (colour)
{
case sColour::RESET:
os << RESET;
break;
case sColour::DEBUG:
os << DEBUG_COLOUR;
break;
case sColour::INFO:
os << INFO_COLOUR;
break;
case sColour::WARNING:
os << WARNING_COLOUR;
break;
case sColour::ERROR:
os << ERROR_COLOUR;
break;
}
}
SwitchColour::SwitchColour(sColour colour, std::ostream &os) : os_(os), colour_(colour)
{
SetColour(colour_, os_);
}
SwitchColour::~SwitchColour()
{
SetColour(sColour::RESET, os_);
}
} // namespace dropshell

View File

@ -0,0 +1,87 @@
#ifndef OUTPUT_HPP
#define OUTPUT_HPP
#include <iostream>
#include <string>
#include <vector>
#include <ostream>
namespace dropshell {
/*
output.hpp and output.cpp - simple output helpers.
Defines ostreams:
debug, info, warning, error.
These ostreams can be used with C++23 print and println, e.g.
std::println(debug, "funny variable: {}={}","my_var",my_var);
Also defines a few helper functions:
PrintDebug(const std::string& msg); // equivalent to std::println(debug, msg);
PrintInfo(const std::string& msg); // equivalent to std::println(info, msg);
PrintWarning(const std::string& msg); // equivalent to std::println(warning, msg);
PrintError(const std::string& msg); // equivalent to std::println(error, msg);
Output for these streams for each line is formatted as:
[DBG] <message>
[INF] <message>
[WRN] <message>
[ERR] <message>
The output is coloured, and the tag is printed in grey.
In addition, when not using any of the above, helper routines for coloring the output of cout and cerr are provided.
SetColour(std::ostream& os, sColour colour);
Where sColour is an enum:
enum class sColour {
RESET,
DEBUG,
INFO,
WARNING,
ERROR
};
*/
// Output streams for different log levels
extern std::ostream& debug;
extern std::ostream& info;
extern std::ostream& warning;
extern std::ostream& error;
extern std::ostream& rawout;
extern std::ostream& rawerr;
// Enum for colours
enum class sColour {
RESET,
DEBUG,
INFO,
WARNING,
ERROR
};
std::ostream& colourstream(sColour colour);
// Set colour for a stream
void SetColour(sColour colour, std::ostream& os = std::cerr);
class SwitchColour
{
public:
SwitchColour(sColour colour, std::ostream& os = std::cerr);
~SwitchColour();
private:
std::ostream& os_;
sColour colour_;
};
} // namespace dropshell
#endif // OUTPUT_HPP

View File

@ -9,6 +9,8 @@
#include <iostream>
#include <map>
#include "output.hpp"
enum kTextColors {
kTextColor_Default,
kTextColor_Red,
@ -32,7 +34,7 @@ const std::map<std::string, coloredText> kReplacements = {
{":tick:", {"+", kTextColor_Green}},
{":cross:", {"x", kTextColor_Red}},
{":warning:", {"!", kTextColor_Yellow}},
{":info:", {"i", kTextColor_Blue}},
{":dropshell::info:", {"i", kTextColor_Blue}},
{":check:", {"+", kTextColor_Green}},
{":x:", {"x", kTextColor_Red}},
{":error:", {"!", kTextColor_Red}},
@ -115,12 +117,12 @@ std::string width_print_centered(std::string str,int width, std::string rowcolor
oss << rowcolor << std::string(lpad, ' ') << process_cell(str, rowcolor) <<
std::string(rpad, ' ') << "\033[0m";
// std::cout << "str = "<<str <<std::endl;
// std::cout << "width = "<<width <<std::endl;
// std::cout << "padding = "<<padding <<std::endl;
// std::cout << "get_visible_length(str) = "<<get_visible_length(str) <<std::endl;
// std::cout << "get_codepoints(str) = "<<get_codepoints(str) <<std::endl;
// std::cout << "oss.str() = ["<<oss.str() <<"]"<<std::endl;
// dropshell::info << "str = "<<str <<std::endl;
// dropshell::info << "width = "<<width <<std::endl;
// dropshell::info << "padding = "<<padding <<std::endl;
// dropshell::info << "get_visible_length(str) = "<<get_visible_length(str) <<std::endl;
// dropshell::info << "get_codepoints(str) = "<<get_codepoints(str) <<std::endl;
// dropshell::info << "oss.str() = ["<<oss.str() <<"]"<<std::endl;
return oss.str();
}
@ -195,98 +197,105 @@ void tableprint::print() {
// Print title if it exists
if (!title.empty()) {
std::cout << "\033[90m"; // Dark grey color for borders
std::cout << "+";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) std::cout << "-";
dropshell::info << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) dropshell::info << "-";
}
std::cout << "+" << std::endl;
dropshell::info << "+" << std::endl;
std::cout << "|"; // White color for title
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "|"; // White color for title
size_t title_width = 0;
for (size_t width : col_widths) {
title_width += width + 2; // +2 for padding
}
title_width += col_widths.size() - 1; // Add space for vertical borders
std::cout << width_print_centered(title,title_width,"\033[1;37m");
std::cout << "\033[90m|" << std::endl;
dropshell::info << width_print_centered(title,title_width,"\033[1;37m");
dropshell::info << "\033[90m|" << std::endl;
// Use └─┴─┘ for bottom of title box to connect with table
std::cout << "+";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) std::cout << "-";
dropshell::info << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) dropshell::info << "-";
}
std::cout << "+" << std::endl;
dropshell::info << "+" << std::endl;
} else {
// Print top border if no title
std::cout << "\033[90m"; // Dark grey color for borders
std::cout << "+";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) std::cout << "+";
dropshell::info << std::string(col_widths[i] + 2, '-');
if (i < rows[0].size() - 1) dropshell::info << "+";
}
std::cout << "+" << std::endl;
dropshell::info << "+" << std::endl;
}
// Print header
std::cout << "|";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "|";
for (size_t i = 0; i < rows[0].size(); ++i) {
std::cout << width_print_centered(rows[0][i],col_widths[i]+2,"\033[1;36m");
dropshell::info << width_print_centered(rows[0][i],col_widths[i]+2,"\033[1;36m");
if (i < rows[0].size() - 1) {
std::cout << "\033[90m|\033[1;36m"; // Border color then back to cyan
dropshell::info << "\033[90m|\033[1;36m"; // Border color then back to cyan
} else {
std::cout << "\033[90m|"; // Just border color for last column
dropshell::info << "\033[90m|"; // Just border color for last column
}
}
std::cout << std::endl;
dropshell::info << std::endl;
// Print header separator
if (!mCompact) {
std::cout << "+";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
for (size_t j = 0; j < col_widths[i] + 2; ++j) {
std::cout << "-";
dropshell::info << "-";
}
if (i < rows[0].size() - 1) std::cout << "+";
if (i < rows[0].size() - 1) dropshell::info << "+";
}
std::cout << "+" << std::endl;
dropshell::info << "+" << std::endl;
}
// Print rows
for (size_t row_idx = 1; row_idx < rows.size(); ++row_idx) {
const auto& row = rows[row_idx];
std::cout << "|";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "|";
for (size_t i = 0; i < row.size(); ++i) {
// Set the appropriate color for the row
std::string rowcolor = (row_idx % 2 == 1) ? "\033[38;5;142m" : "\033[38;5;250m";
std::cout << width_print_left(row[i],col_widths[i]+2,rowcolor);
std::cout << "\033[90m" << "|";
dropshell::info << width_print_left(row[i],col_widths[i]+2,rowcolor);
dropshell::info << "\033[90m" << "|";
}
std::cout << std::endl;
dropshell::info << std::endl;
// Print row separator if not the last row
if (row_idx < rows.size() - 1 && !mCompact) {
std::cout << "+";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
for (size_t j = 0; j < col_widths[i] + 2; ++j) {
std::cout << "-";
dropshell::info << "-";
}
if (i < rows[0].size() - 1) std::cout << "+";
if (i < rows[0].size() - 1) dropshell::info << "+";
}
std::cout << "+" << std::endl;
dropshell::info << "+" << std::endl;
}
}
// Print bottom border
std::cout << "+";
dropshell::info << "\033[90m"; // Dark grey color for borders
dropshell::info << "+";
for (size_t i = 0; i < rows[0].size(); ++i) {
for (size_t j = 0; j < col_widths[i] + 2; ++j) {
std::cout << "-";
dropshell::info << "-";
}
if (i < rows[0].size() - 1) std::cout << "+";
if (i < rows[0].size() - 1) dropshell::info << "+";
}
std::cout << "+" << "\033[0m" << std::endl; // Reset color
dropshell::info << "+" << "\033[0m" << std::endl; // Reset color
}

View File

@ -7,13 +7,25 @@
#include <filesystem>
#include <regex>
#include <random>
#include <sys/ioctl.h>
#include <unistd.h>
namespace dropshell {
void maketitle(const std::string& title) {
std::cout << std::string(title.length() + 4, '-') << std::endl;
std::cout << "| " << title << " |" << std::endl;
std::cout << std::string(title.length() + 4, '-') << std::endl;
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) {
colourstream(colour) << std::string(title.length() + 4, '-') << std::endl;
colourstream(colour) << "| " << title << " |" << std::endl;
colourstream(colour) << std::string(title.length() + 4, '-') << std::endl;
}
bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line) {
@ -276,29 +288,6 @@ std::vector<std::string> split(const std::string& str, const std::string& delimi
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)
{
static std::mt19937 generator(std::random_device{}());
@ -334,20 +323,6 @@ std::string safearg(int argc, char *argv[], int index)
return argv[index];
}
void print_left_aligned(const std::string & str, int width) {
std::cout << left_align(str, width);
}
void print_centered(const std::string & str, int width) {
std::cout << center_align(str, width);
}
void print_right_aligned(const std::string & str, int width) {
std::cout << right_align(str, width);
}
std::string left_align(const std::string & str, int width) {
if (static_cast<int>(str.size()) >= width)
return str;
@ -370,5 +345,91 @@ 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';
}
} // namespace dropshell

View File

@ -2,6 +2,9 @@
#include <string>
#include <vector>
#include <map>
#include "output.hpp"
namespace dropshell {
@ -10,10 +13,12 @@ namespace dropshell {
*
* @param title The title string to display
*/
void maketitle(const std::string& title);
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);
std::string magic_string();
bool has_magic_string(std::string name);
// utility functions
std::string trim(std::string str);
@ -37,20 +42,21 @@ void ensure_directories_exist(std::vector<std::string> directories);
std::vector<int> search(const std::string &pat, const std::string &txt);
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);
int die(const std::string & msg);
std::string safearg(int argc, char *argv[], int index);
std::string safearg(const std::vector<std::string> & args, int index);
void print_left_aligned(const std::string & str, int width);
void print_centered(const std::string & str, int width);
void print_right_aligned(const std::string & str, int width);
std::string left_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 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);
} // namespace dropshell