Compare commits

...

12 Commits

Author SHA1 Message Date
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
29 changed files with 1315 additions and 624 deletions

View File

@ -73,6 +73,7 @@ function run_command() {
# update the main variables. # update the main variables.
CONFIG_PATH="${service_path}/config" CONFIG_PATH="${service_path}/config"
SERVICE="${SERVICE_NAME}" SERVICE="${SERVICE_NAME}"
DOCKER_CLI_HINTS=false
set +a 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 # Get all service names
SERVICE_NAMES=$(ls "${SERVICES_PATH}") SERVICE_NAMES=$(ls "${SERVICES_PATH}")

View File

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

View File

@ -281,36 +281,39 @@ bool recreate_tree(std::string destination_folder) {
"YXRofS9jb25maWcvc2VydmljZS5lbnYiCiAgICAgICAgbG9hZF9kb3RlbnYgIiR7c2VydmljZV9w"\ "YXRofS9jb25maWcvc2VydmljZS5lbnYiCiAgICAgICAgbG9hZF9kb3RlbnYgIiR7c2VydmljZV9w"\
"YXRofS9jb25maWcvLnRlbXBsYXRlX2luZm8uZW52IgoKICAgICAgICAjIHVwZGF0ZSB0aGUgbWFp"\ "YXRofS9jb25maWcvLnRlbXBsYXRlX2luZm8uZW52IgoKICAgICAgICAjIHVwZGF0ZSB0aGUgbWFp"\
"biB2YXJpYWJsZXMuCiAgICAgICAgQ09ORklHX1BBVEg9IiR7c2VydmljZV9wYXRofS9jb25maWci"\ "biB2YXJpYWJsZXMuCiAgICAgICAgQ09ORklHX1BBVEg9IiR7c2VydmljZV9wYXRofS9jb25maWci"\
"CiAgICAgICAgU0VSVklDRT0iJHtTRVJWSUNFX05BTUV9IgoKICAgICAgICBzZXQgK2EKCiAgICAg"\ "CiAgICAgICAgU0VSVklDRT0iJHtTRVJWSUNFX05BTUV9IgogICAgICAgIERPQ0tFUl9DTElfSElO"\
"ICAgX2NoZWNrX3JlcXVpcmVkX2Vudl92YXJzX2FsbHNlcnZpY2Vzc3RhdHVzICJDT05GSUdfUEFU"\ "VFM9ZmFsc2UKCiAgICAgICAgc2V0ICthCgogICAgICAgIF9jaGVja19yZXF1aXJlZF9lbnZfdmFy"\
"SCIgIlNFUlZFUiIgIlNFUlZJQ0UiICJBR0VOVF9QQVRIIiAiSE9TVF9OQU1FIiAiVEVNUExBVEUi"\ "c19hbGxzZXJ2aWNlc3N0YXR1cyAiQ09ORklHX1BBVEgiICJTRVJWRVIiICJTRVJWSUNFIiAiQUdF"\
"CgogICAgICAgIGlmIFsgIiRjYXB0dXJlX291dHB1dCIgPSAidHJ1ZSIgXTsgdGhlbgogICAgICAg"\ "TlRfUEFUSCIgIkhPU1RfTkFNRSIgIlRFTVBMQVRFIgoKICAgICAgICBpZiBbICIkY2FwdHVyZV9v"\
"ICAgICAjIENhcHR1cmUgYW5kIHJldHVybiBvdXRwdXQKICAgICAgICAgICAgYmFzaCAiJHtzZXJ2"\ "dXRwdXQiID0gInRydWUiIF07IHRoZW4KICAgICAgICAgICAgIyBDYXB0dXJlIGFuZCByZXR1cm4g"\
"aWNlX3BhdGh9L3RlbXBsYXRlLyR7Y29tbWFuZH0uc2giIDI+JjEKICAgICAgICBlbHNlCiAgICAg"\ "b3V0cHV0CiAgICAgICAgICAgIGJhc2ggIiR7c2VydmljZV9wYXRofS90ZW1wbGF0ZS8ke2NvbW1h"\
"ICAgICAgICMgUnVuIHNpbGVudGx5IGFuZCByZXR1cm4gZXhpdCBjb2RlCiAgICAgICAgICAgIGJh"\ "bmR9LnNoIiAyPiYxCiAgICAgICAgZWxzZQogICAgICAgICAgICAjIFJ1biBzaWxlbnRseSBhbmQg"\
"c2ggIiR7c2VydmljZV9wYXRofS90ZW1wbGF0ZS8ke2NvbW1hbmR9LnNoIiA+IC9kZXYvbnVsbCAy"\ "cmV0dXJuIGV4aXQgY29kZQogICAgICAgICAgICBiYXNoICIke3NlcnZpY2VfcGF0aH0vdGVtcGxh"\
"PiYxCiAgICAgICAgZmkKICAgICkKICAgIENVUlJFTlRfRVhJVF9DT0RFPSQ/Cn0KCmZ1bmN0aW9u"\ "dGUvJHtjb21tYW5kfS5zaCIgPiAvZGV2L251bGwgMj4mMQogICAgICAgIGZpCiAgICApCiAgICBD"\
"IGNvbW1hbmRfZXhpc3RzKCkgewogICAgbG9jYWwgc2VydmljZV9wYXRoPSQxCiAgICBsb2NhbCBj"\ "VVJSRU5UX0VYSVRfQ09ERT0kPwp9CgpmdW5jdGlvbiBjb21tYW5kX2V4aXN0cygpIHsKICAgIGxv"\
"b21tYW5kPSQyCiAgICBpZiBbICEgLWYgIiR7c2VydmljZV9wYXRofS90ZW1wbGF0ZS8ke2NvbW1h"\ "Y2FsIHNlcnZpY2VfcGF0aD0kMQogICAgbG9jYWwgY29tbWFuZD0kMgogICAgaWYgWyAhIC1mICIk"\
"bmR9LnNoIiBdOyB0aGVuCiAgICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICByZXR1cm4gMAp9CgoK"\ "e3NlcnZpY2VfcGF0aH0vdGVtcGxhdGUvJHtjb21tYW5kfS5zaCIgXTsgdGhlbgogICAgICAgIHJl"\
"CiMgR2V0IGFsbCBzZXJ2aWNlIG5hbWVzClNFUlZJQ0VfTkFNRVM9JChscyAiJHtTRVJWSUNFU19Q"\ "dHVybiAxCiAgICBmaQogICAgcmV0dXJuIDAKfQoKCmlmIFsgISAtZCAiJHtTRVJWSUNFU19QQVRI"\
"QVRIfSIpCgojIEl0ZXJhdGUgb3ZlciBhbGwgc2VydmljZSBuYW1lcwpmb3IgU0VSVklDRV9OQU1F"\ "fSIgXTsgdGhlbgogICAgZWNobyAiU2VydmljZXMgcGF0aCBkb2VzIG5vdCBleGlzdDogJHtTRVJW"\
"IGluICR7U0VSVklDRV9OQU1FU307IGRvCgogICAgU0VSVklDRV9QQVRIPSQocmVhbHBhdGggIiR7"\ "SUNFU19QQVRIfSIKICAgIGV4aXQgMApmaQoKIyBHZXQgYWxsIHNlcnZpY2UgbmFtZXMKU0VSVklD"\
"U0VSVklDRVNfUEFUSH0vJHtTRVJWSUNFX05BTUV9IikKCiAgICAjLS0tLS0tLS0tLS0tLS0tLS0t"\ "RV9OQU1FUz0kKGxzICIke1NFUlZJQ0VTX1BBVEh9IikKCiMgSXRlcmF0ZSBvdmVyIGFsbCBzZXJ2"\
"LS0tLS0tLS0tLS0tLS0KICAgICMgR2V0IHRoZSBzZXJ2aWNlIGhlYWx0aAogICAgaWYgISBjb21t"\ "aWNlIG5hbWVzCmZvciBTRVJWSUNFX05BTUUgaW4gJHtTRVJWSUNFX05BTUVTfTsgZG8KCiAgICBT"\
"YW5kX2V4aXN0cyAiJHtTRVJWSUNFX1BBVEh9IiAic3RhdHVzIjsgdGhlbgogICAgICAgIFNFUlZJ"\ "RVJWSUNFX1BBVEg9JChyZWFscGF0aCAiJHtTRVJWSUNFU19QQVRIfS8ke1NFUlZJQ0VfTkFNRX0i"\
"Q0VfSEVBTFRIPSJ1bmtub3duIgogICAgZWxzZQogICAgICAgIHJ1bl9jb21tYW5kICIke1NFUlZJ"\ "KQoKICAgICMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgIyBHZXQgdGhlIHNl"\
"Q0VfUEFUSH0iICJzdGF0dXMiICJmYWxzZSIKICAgICAgICBpZiBbICIke0NVUlJFTlRfRVhJVF9D"\ "cnZpY2UgaGVhbHRoCiAgICBpZiAhIGNvbW1hbmRfZXhpc3RzICIke1NFUlZJQ0VfUEFUSH0iICJz"\
"T0RFfSIgLWVxIDAgXTsgdGhlbgogICAgICAgICAgICBTRVJWSUNFX0hFQUxUSD0iaGVhbHRoeSIK"\ "dGF0dXMiOyB0aGVuCiAgICAgICAgU0VSVklDRV9IRUFMVEg9InVua25vd24iCiAgICBlbHNlCiAg"\
"ICAgICAgICBlbHNlCiAgICAgICAgICAgIFNFUlZJQ0VfSEVBTFRIPSJ1bmhlYWx0aHkiCiAgICAg"\ "ICAgICAgcnVuX2NvbW1hbmQgIiR7U0VSVklDRV9QQVRIfSIgInN0YXR1cyIgImZhbHNlIgogICAg"\
"ICAgZmkKICAgIGZpCgogICAgIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICAj"\ "ICAgIGlmIFsgIiR7Q1VSUkVOVF9FWElUX0NPREV9IiAtZXEgMCBdOyB0aGVuCiAgICAgICAgICAg"\
"IEdldCB0aGUgc2VydmljZSBwb3J0cwogICAgaWYgISBjb21tYW5kX2V4aXN0cyAiJHtTRVJWSUNF"\ "IFNFUlZJQ0VfSEVBTFRIPSJoZWFsdGh5IgogICAgICAgIGVsc2UKICAgICAgICAgICAgU0VSVklD"\
"X1BBVEh9IiAicG9ydHMiOyB0aGVuCiAgICAgICAgU0VSVklDRV9QT1JUUz0iIgogICAgZWxzZQog"\ "RV9IRUFMVEg9InVuaGVhbHRoeSIKICAgICAgICBmaQogICAgZmkKCiAgICAjLS0tLS0tLS0tLS0t"\
"ICAgICAgIHJ1bl9jb21tYW5kICIke1NFUlZJQ0VfUEFUSH0iICJwb3J0cyIgInRydWUiCiAgICAg"\ "LS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgICMgR2V0IHRoZSBzZXJ2aWNlIHBvcnRzCiAgICBpZiAh"\
"ICAgU0VSVklDRV9QT1JUUz0iJHtDVVJSRU5UX09VVFBVVH0iCiAgICBmaQoKICAgICMtLS0tLS0t"\ "IGNvbW1hbmRfZXhpc3RzICIke1NFUlZJQ0VfUEFUSH0iICJwb3J0cyI7IHRoZW4KICAgICAgICBT"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgIyByZXR1cm4gdGhlIGhlYWx0aCBhbmQgcG9y"\ "RVJWSUNFX1BPUlRTPSIiCiAgICBlbHNlCiAgICAgICAgcnVuX2NvbW1hbmQgIiR7U0VSVklDRV9Q"\
"dHMKICAgIGVjaG8gIiR7U0VSVklDRV9OQU1FfV9IRUFMVEg9JHtTRVJWSUNFX0hFQUxUSH0iCiAg"\ "QVRIfSIgInBvcnRzIiAidHJ1ZSIKICAgICAgICBTRVJWSUNFX1BPUlRTPSIke0NVUlJFTlRfT1VU"\
"ICBlY2hvICIke1NFUlZJQ0VfTkFNRX1fUE9SVFM9JHtTRVJWSUNFX1BPUlRTfSIKZG9uZQo="; "UFVUfSIKICAgIGZpCgogICAgIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICAj"\
"IHJldHVybiB0aGUgaGVhbHRoIGFuZCBwb3J0cwogICAgZWNobyAiJHtTRVJWSUNFX05BTUV9X0hF"\
"QUxUSD0ke1NFUlZJQ0VfSEVBTFRIfSIKICAgIGVjaG8gIiR7U0VSVklDRV9OQU1FfV9QT1JUUz0k"\
"e1NFUlZJQ0VfUE9SVFN9Igpkb25lCg==";
// Decode Base64 data // Decode Base64 data
size_t decoded_size = (strlen(filedata_base64) * 3) / 4; size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
@ -318,7 +321,7 @@ bool recreate_tree(std::string destination_folder) {
size_t actual_size; size_t actual_size;
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &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); bool file_written = _recreate_file_(outpath, 4669115953916396805ULL, std::filesystem::perms(493), decoded_data, actual_size);
delete[] decoded_data; delete[] decoded_data;
any_written = any_written || file_written; any_written = any_written || file_written;
} }
@ -360,73 +363,69 @@ bool recreate_tree(std::string destination_folder) {
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\ "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t"\
"LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFByaW50cyBh"\ "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFByaW50cyBh"\
"biBlcnJvciBtZXNzYWdlIGluIHJlZCBhbmQgZXhpdHMgd2l0aCBzdGF0dXMgY29kZSAxLgpfZGll"\ "biBlcnJvciBtZXNzYWdlIGluIHJlZCBhbmQgZXhpdHMgd2l0aCBzdGF0dXMgY29kZSAxLgpfZGll"\
"KCkgewogICAgZWNobyAtZSAiXDAzM1s5MW1FcnJvcjogJDFcMDMzWzBtIgogICAgZXhpdCAxCn0K"\ "KCkgewogICAgZWNobyAtZSAiRXJyb3I6ICQxIgogICAgZXhpdCAxCn0KCiMgQ3JlYXRlcy9zdGFy"\
"CiMgU3dpdGNoZXMgdGVybWluYWwgb3V0cHV0IGNvbG9yIHRvIGdyZXkuCl9ncmV5X3N0YXJ0KCkg"\ "dHMgYSBjb250YWluZXIsIHZlcmlmeWluZyBpdCBydW5zLgpfY3JlYXRlX2FuZF9zdGFydF9jb250"\
"ewogICAgZWNobyAtZSAtbiAiXDAzM1s5MG0iCn0KCiMgUmVzZXRzIHRlcm1pbmFsIG91dHB1dCBj"\ "YWluZXIoKSB7CiAgICBpZiBbIC16ICIkMSIgXSB8fCBbIC16ICIkMiIgXTsgdGhlbgogICAgICAg"\
"b2xvciBmcm9tIGdyZXkuCl9ncmV5X2VuZCgpIHsKICAgIGVjaG8gLWUgLW4gIlwwMzNbMG0iCn0K"\ "IF9kaWUgIlRlbXBsYXRlIGVycm9yOiBjcmVhdGVfYW5kX3N0YXJ0X2NvbnRhaW5lciA8cnVuX2Nt"\
"CiMgQ3JlYXRlcy9zdGFydHMgYSBjb250YWluZXIsIHZlcmlmeWluZyBpdCBydW5zLgpfY3JlYXRl"\ "ZD4gPGNvbnRhaW5lcl9uYW1lPiIKICAgIGZpCgogICAgbG9jYWwgcnVuX2NtZD0iJDEiCiAgICBs"\
"X2FuZF9zdGFydF9jb250YWluZXIoKSB7CiAgICBpZiBbIC16ICIkMSIgXSB8fCBbIC16ICIkMiIg"\ "b2NhbCBjb250YWluZXJfbmFtZT0iJDIiCgogICAgaWYgX2lzX2NvbnRhaW5lcl9leGlzdHMgJGNv"\
"XTsgdGhlbgogICAgICAgIF9kaWUgIlRlbXBsYXRlIGVycm9yOiBjcmVhdGVfYW5kX3N0YXJ0X2Nv"\ "bnRhaW5lcl9uYW1lOyB0aGVuCiAgICAgICAgX2lzX2NvbnRhaW5lcl9ydW5uaW5nICRjb250YWlu"\
"bnRhaW5lciA8cnVuX2NtZD4gPGNvbnRhaW5lcl9uYW1lPiIKICAgIGZpCgogICAgbG9jYWwgcnVu"\ "ZXJfbmFtZSAmJiByZXR1cm4gMAogICAgICAgIF9zdGFydF9jb250YWluZXIgJGNvbnRhaW5lcl9u"\
"X2NtZD0iJDEiCiAgICBsb2NhbCBjb250YWluZXJfbmFtZT0iJDIiCgogICAgaWYgX2lzX2NvbnRh"\ "YW1lCiAgICBlbHNlCiAgICAgICAgJHJ1bl9jbWQKICAgIGZpCgogICAgaWYgISBfaXNfY29udGFp"\
"aW5lcl9leGlzdHMgJGNvbnRhaW5lcl9uYW1lOyB0aGVuCiAgICAgICAgX2lzX2NvbnRhaW5lcl9y"\ "bmVyX3J1bm5pbmcgJGNvbnRhaW5lcl9uYW1lOyB0aGVuCiAgICAgICAgX2RpZSAiQ29udGFpbmVy"\
"dW5uaW5nICRjb250YWluZXJfbmFtZSAmJiByZXR1cm4gMAogICAgICAgIF9zdGFydF9jb250YWlu"\ "ICR7Y29udGFpbmVyX25hbWV9IGZhaWxlZCB0byBzdGFydCIKICAgIGZpCgogICAgSUQ9JChfZ2V0"\
"ZXIgJGNvbnRhaW5lcl9uYW1lCiAgICBlbHNlCiAgICAgICAgX2dyZXlfc3RhcnQKICAgICAgICAk"\ "X2NvbnRhaW5lcl9pZCAkY29udGFpbmVyX25hbWUpCiAgICBlY2hvICJDb250YWluZXIgJHtjb250"\
"cnVuX2NtZAogICAgICAgIF9ncmV5X2VuZAogICAgZmkKCiAgICBpZiAhIF9pc19jb250YWluZXJf"\ "YWluZXJfbmFtZX0gaXMgcnVubmluZyB3aXRoIElEICR7SUR9Igp9CgojIENyZWF0ZXMgYSBkaXJl"\
"cnVubmluZyAkY29udGFpbmVyX25hbWU7IHRoZW4KICAgICAgICBfZGllICJDb250YWluZXIgJHtj"\ "Y3RvcnkgaWYgaXQgZG9lc24ndCBleGlzdCAoY2htb2QgNzc3KS4KX2NyZWF0ZV9mb2xkZXIoKSB7"\
"b250YWluZXJfbmFtZX0gZmFpbGVkIHRvIHN0YXJ0IgogICAgZmkKCiAgICBJRD0kKF9nZXRfY29u"\ "CiAgICBsb2NhbCBmb2xkZXI9IiQxIgogICAgaWYgWyAtZCAiJGZvbGRlciIgXTsgdGhlbgogICAg"\
"dGFpbmVyX2lkICRjb250YWluZXJfbmFtZSkKICAgIGVjaG8gIkNvbnRhaW5lciAke2NvbnRhaW5l"\ "ICAgIHJldHVybiAwCiAgICBmaQogICAgaWYgISBta2RpciAtcCAiJGZvbGRlciI7IHRoZW4KICAg"\
"cl9uYW1lfSBpcyBydW5uaW5nIHdpdGggSUQgJHtJRH0iCn0KCiMgQ3JlYXRlcyBhIGRpcmVjdG9y"\ "ICAgICBfZGllICJGYWlsZWQgdG8gY3JlYXRlIGZvbGRlcjogJGZvbGRlciIKICAgIGZpCiAgICBj"\
"eSBpZiBpdCBkb2Vzbid0IGV4aXN0IChjaG1vZCA3NzcpLgpfY3JlYXRlX2ZvbGRlcigpIHsKICAg"\ "aG1vZCA3NzcgIiRmb2xkZXIiCiAgICBlY2hvICJGb2xkZXIgY3JlYXRlZDogJGZvbGRlciIKfQoK"\
"IGxvY2FsIGZvbGRlcj0iJDEiCiAgICBpZiBbIC1kICIkZm9sZGVyIiBdOyB0aGVuCiAgICAgICAg"\ "IyBDaGVja3MgaWYgRG9ja2VyIGlzIGluc3RhbGxlZCwgcnVubmluZywgYW5kIHVzZXIgaGFzIHBl"\
"cmV0dXJuIDAKICAgIGZpCiAgICBpZiAhIG1rZGlyIC1wICIkZm9sZGVyIjsgdGhlbgogICAgICAg"\ "cm1pc3Npb24uIFJldHVybnMgMSBvbiBmYWlsdXJlLgpfY2hlY2tfZG9ja2VyX2luc3RhbGxlZCgp"\
"IF9kaWUgIkZhaWxlZCB0byBjcmVhdGUgZm9sZGVyOiAkZm9sZGVyIgogICAgZmkKICAgIGNobW9k"\ "IHsKICAgIGlmICEgY29tbWFuZCAtdiBkb2NrZXIgJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAg"\
"IDc3NyAiJGZvbGRlciIKICAgIGVjaG8gIkZvbGRlciBjcmVhdGVkOiAkZm9sZGVyIgp9CgojIENo"\ "ZWNobyAiRG9ja2VyIGlzIG5vdCBpbnN0YWxsZWQiCiAgICAgICAgcmV0dXJuIDEKICAgIGZpCgog"\
"ZWNrcyBpZiBEb2NrZXIgaXMgaW5zdGFsbGVkLCBydW5uaW5nLCBhbmQgdXNlciBoYXMgcGVybWlz"\ "ICAgIyBjaGVjayBpZiBkb2NrZXIgZGFlbW9uIGlzIHJ1bm5pbmcKICAgIGlmICEgZG9ja2VyIGlu"\
"c2lvbi4gUmV0dXJucyAxIG9uIGZhaWx1cmUuCl9jaGVja19kb2NrZXJfaW5zdGFsbGVkKCkgewog"\ "Zm8gJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAgZWNobyAiRG9ja2VyIGRhZW1vbiBpcyBub3Qg"\
"ICAgaWYgISBjb21tYW5kIC12IGRvY2tlciAmPiAvZGV2L251bGw7IHRoZW4KICAgICAgICBlY2hv"\ "cnVubmluZyIKICAgICAgICByZXR1cm4gMQogICAgZmkKCiAgICAjIGNoZWNrIGlmIHVzZXIgaGFz"\
"ICJEb2NrZXIgaXMgbm90IGluc3RhbGxlZCIKICAgICAgICByZXR1cm4gMQogICAgZmkKCiAgICAj"\ "IHBlcm1pc3Npb24gdG8gcnVuIGRvY2tlcgogICAgaWYgISBkb2NrZXIgcnVuIC0tcm0gaGVsbG8t"\
"IGNoZWNrIGlmIGRvY2tlciBkYWVtb24gaXMgcnVubmluZwogICAgaWYgISBkb2NrZXIgaW5mbyAm"\ "d29ybGQgJj4gL2Rldi9udWxsOyB0aGVuCiAgICAgICAgZWNobyAiVXNlciBkb2VzIG5vdCBoYXZl"\
"PiAvZGV2L251bGw7IHRoZW4KICAgICAgICBlY2hvICJEb2NrZXIgZGFlbW9uIGlzIG5vdCBydW5u"\ "IHBlcm1pc3Npb24gdG8gcnVuIGRvY2tlciIKICAgICAgICByZXR1cm4gMQogICAgZmkKCiAgICBy"\
"aW5nIgogICAgICAgIHJldHVybiAxCiAgICBmaQoKICAgICMgY2hlY2sgaWYgdXNlciBoYXMgcGVy"\ "ZXR1cm4gMAp9CgojIENoZWNrcyBpZiBhIGNvbnRhaW5lciAoYW55IHN0YXRlKSBleGlzdHMuIFJl"\
"bWlzc2lvbiB0byBydW4gZG9ja2VyCiAgICBpZiAhIGRvY2tlciBydW4gLS1ybSBoZWxsby13b3Js"\ "dHVybnMgMSBpZiBub3QgZm91bmQuCl9pc19jb250YWluZXJfZXhpc3RzKCkgewogICAgaWYgISBk"\
"ZCAmPiAvZGV2L251bGw7IHRoZW4KICAgICAgICBlY2hvICJVc2VyIGRvZXMgbm90IGhhdmUgcGVy"\ "b2NrZXIgcHMgLWEgLS1mb3JtYXQgInt7Lk5hbWVzfX0iIHwgZ3JlcCAtcSAiXiQxJCI7IHRoZW4K"\
"bWlzc2lvbiB0byBydW4gZG9ja2VyIgogICAgICAgIHJldHVybiAxCiAgICBmaQoKICAgIHJldHVy"\ "ICAgICAgICByZXR1cm4gMQogICAgZmkKICAgIHJldHVybiAwCn0KCiMgQ2hlY2tzIGlmIGEgY29u"\
"biAwCn0KCiMgQ2hlY2tzIGlmIGEgY29udGFpbmVyIChhbnkgc3RhdGUpIGV4aXN0cy4gUmV0dXJu"\ "dGFpbmVyIGlzIGN1cnJlbnRseSBydW5uaW5nLiBSZXR1cm5zIDEgaWYgbm90IHJ1bm5pbmcuCl9p"\
"cyAxIGlmIG5vdCBmb3VuZC4KX2lzX2NvbnRhaW5lcl9leGlzdHMoKSB7CiAgICBpZiAhIGRvY2tl"\ "c19jb250YWluZXJfcnVubmluZygpIHsKICAgIGlmICEgZG9ja2VyIHBzIC0tZm9ybWF0ICJ7ey5O"\
"ciBwcyAtYSAtLWZvcm1hdCAie3suTmFtZXN9fSIgfCBncmVwIC1xICJeJDEkIjsgdGhlbgogICAg"\ "YW1lc319IiB8IGdyZXAgLXEgIl4kMSQiOyB0aGVuCiAgICAgICAgcmV0dXJuIDEKICAgIGZpCiAg"\
"ICAgIHJldHVybiAxCiAgICBmaQogICAgcmV0dXJuIDAKfQoKIyBDaGVja3MgaWYgYSBjb250YWlu"\ "ICByZXR1cm4gMAp9CgojIFByaW50cyB0aGUgSUQgb2YgdGhlIG5hbWVkIGNvbnRhaW5lci4KX2dl"\
"ZXIgaXMgY3VycmVudGx5IHJ1bm5pbmcuIFJldHVybnMgMSBpZiBub3QgcnVubmluZy4KX2lzX2Nv"\ "dF9jb250YWluZXJfaWQoKSB7CiAgICBkb2NrZXIgcHMgLS1mb3JtYXQgInt7LklEfX0iIC0tZmls"\
"bnRhaW5lcl9ydW5uaW5nKCkgewogICAgaWYgISBkb2NrZXIgcHMgLS1mb3JtYXQgInt7Lk5hbWVz"\ "dGVyICJuYW1lPSQxIgp9CgojIFByaW50cyB0aGUgc3RhdHVzIHN0cmluZyBvZiB0aGUgbmFtZWQg"\
"fX0iIHwgZ3JlcCAtcSAiXiQxJCI7IHRoZW4KICAgICAgICByZXR1cm4gMQogICAgZmkKICAgIHJl"\ "Y29udGFpbmVyLgpfZ2V0X2NvbnRhaW5lcl9zdGF0dXMoKSB7CiAgICBkb2NrZXIgcHMgLS1mb3Jt"\
"dHVybiAwCn0KCiMgUHJpbnRzIHRoZSBJRCBvZiB0aGUgbmFtZWQgY29udGFpbmVyLgpfZ2V0X2Nv"\ "YXQgInt7LlN0YXR1c319IiAtLWZpbHRlciAibmFtZT0kMSIKfQoKIyBTdGFydHMgYW4gZXhpc3Rp"\
"bnRhaW5lcl9pZCgpIHsKICAgIGRvY2tlciBwcyAtLWZvcm1hdCAie3suSUR9fSIgLS1maWx0ZXIg"\ "bmcsIHN0b3BwZWQgY29udGFpbmVyLgpfc3RhcnRfY29udGFpbmVyKCkgewogICAgX2lzX2NvbnRh"\
"Im5hbWU9JDEiCn0KCiMgUHJpbnRzIHRoZSBzdGF0dXMgc3RyaW5nIG9mIHRoZSBuYW1lZCBjb250"\ "aW5lcl9leGlzdHMgJDEgfHwgcmV0dXJuIDEKICAgIF9pc19jb250YWluZXJfcnVubmluZyAkMSAm"\
"YWluZXIuCl9nZXRfY29udGFpbmVyX3N0YXR1cygpIHsKICAgIGRvY2tlciBwcyAtLWZvcm1hdCAi"\ "JiByZXR1cm4gMAogICAgZG9ja2VyIHN0YXJ0ICQxCn0KCiMgU3RvcHMgYSBydW5uaW5nIGNvbnRh"\
"e3suU3RhdHVzfX0iIC0tZmlsdGVyICJuYW1lPSQxIgp9CgojIFN0YXJ0cyBhbiBleGlzdGluZywg"\ "aW5lci4KX3N0b3BfY29udGFpbmVyKCkgewogICAgX2lzX2NvbnRhaW5lcl9ydW5uaW5nICQxIHx8"\
"c3RvcHBlZCBjb250YWluZXIuCl9zdGFydF9jb250YWluZXIoKSB7CiAgICBfaXNfY29udGFpbmVy"\ "IHJldHVybiAwOwogICAgZG9ja2VyIHN0b3AgJDEKfSAgIAoKIyBTdG9wcyAoaWYgbmVlZGVkKSBh"\
"X2V4aXN0cyAkMSB8fCByZXR1cm4gMQogICAgX2lzX2NvbnRhaW5lcl9ydW5uaW5nICQxICYmIHJl"\ "bmQgcmVtb3ZlcyBhIGNvbnRhaW5lci4KX3JlbW92ZV9jb250YWluZXIoKSB7CiAgICBfc3RvcF9j"\
"dHVybiAwCiAgICBkb2NrZXIgc3RhcnQgJDEKfQoKIyBTdG9wcyBhIHJ1bm5pbmcgY29udGFpbmVy"\ "b250YWluZXIgJDEKICAgIF9pc19jb250YWluZXJfZXhpc3RzICQxIHx8IHJldHVybiAwOwogICAg"\
"Lgpfc3RvcF9jb250YWluZXIoKSB7CiAgICBfaXNfY29udGFpbmVyX3J1bm5pbmcgJDEgfHwgcmV0"\ "ZG9ja2VyIHJtICQxCn0KCiMgUHJpbnRzIHRoZSBsb2dzIGZvciBhIGNvbnRhaW5lci4KX2dldF9j"\
"dXJuIDA7CiAgICBkb2NrZXIgc3RvcCAkMQp9ICAgCgojIFN0b3BzIChpZiBuZWVkZWQpIGFuZCBy"\ "b250YWluZXJfbG9ncygpIHsKICAgIGlmICEgX2lzX2NvbnRhaW5lcl9leGlzdHMgJDE7IHRoZW4K"\
"ZW1vdmVzIGEgY29udGFpbmVyLgpfcmVtb3ZlX2NvbnRhaW5lcigpIHsKICAgIF9zdG9wX2NvbnRh"\ "ICAgICAgICBlY2hvICJDb250YWluZXIgJDEgZG9lcyBub3QgZXhpc3QiCiAgICAgICAgcmV0dXJu"\
"aW5lciAkMQogICAgX2lzX2NvbnRhaW5lcl9leGlzdHMgJDEgfHwgcmV0dXJuIDA7CiAgICBkb2Nr"\ "IDEKICAgIGZpCgogICAgZG9ja2VyIGxvZ3MgJDEKfQoKIyBDaGVja3MgaWYgbGlzdGVkIGVudmly"\
"ZXIgcm0gJDEKfQoKIyBQcmludHMgdGhlIGxvZ3MgZm9yIGEgY29udGFpbmVyLgpfZ2V0X2NvbnRh"\ "b25tZW50IHZhcmlhYmxlcyBhcmUgc2V0OyBjYWxscyBfZGllKCkgaWYgYW55IGFyZSBtaXNzaW5n"\
"aW5lcl9sb2dzKCkgewogICAgaWYgISBfaXNfY29udGFpbmVyX2V4aXN0cyAkMTsgdGhlbgogICAg"\ "LgpfY2hlY2tfcmVxdWlyZWRfZW52X3ZhcnMoKSB7CiAgICBsb2NhbCByZXF1aXJlZF92YXJzPSgi"\
"ICAgIGVjaG8gIkNvbnRhaW5lciAkMSBkb2VzIG5vdCBleGlzdCIKICAgICAgICByZXR1cm4gMQog"\ "JEAiKQogICAgZm9yIHZhciBpbiAiJHtyZXF1aXJlZF92YXJzW0BdfSI7IGRvCiAgICAgICAgaWYg"\
"ICAgZmkKCiAgICBkb2NrZXIgbG9ncyAkMQp9CgojIENoZWNrcyBpZiBsaXN0ZWQgZW52aXJvbm1l"\ "WyAteiAiJHshdmFyfSIgXTsgdGhlbgogICAgICAgICAgICBfZGllICJSZXF1aXJlZCBlbnZpcm9u"\
"bnQgdmFyaWFibGVzIGFyZSBzZXQ7IGNhbGxzIF9kaWUoKSBpZiBhbnkgYXJlIG1pc3NpbmcuCl9j"\ "bWVudCB2YXJpYWJsZSAkdmFyIGlzIG5vdCBzZXQiCiAgICAgICAgZmkKICAgIGRvbmUKfQoKIyBS"\
"aGVja19yZXF1aXJlZF9lbnZfdmFycygpIHsKICAgIGxvY2FsIHJlcXVpcmVkX3ZhcnM9KCIkQCIp"\ "ZW1vdmVzIGEgcGF0aCB1c2luZyBhIHJvb3QgRG9ja2VyIGNvbnRhaW5lciAoZm9yIHBlcm1pc3Np"\
"CiAgICBmb3IgdmFyIGluICIke3JlcXVpcmVkX3ZhcnNbQF19IjsgZG8KICAgICAgICBpZiBbIC16"\ "b25zKS4KX3Jvb3RfcmVtb3ZlX3RyZWUoKSB7CiAgICBsb2NhbCB0b19yZW1vdmU9IiQxIgogICAg"\
"ICIkeyF2YXJ9IiBdOyB0aGVuCiAgICAgICAgICAgIF9kaWUgIlJlcXVpcmVkIGVudmlyb25tZW50"\ "cGFyZW50PSQoZGlybmFtZSAiJHRvX3JlbW92ZSIpCiAgICBhYnNfcGFyZW50PSQocmVhbHBhdGgg"\
"IHZhcmlhYmxlICR2YXIgaXMgbm90IHNldCIKICAgICAgICBmaQogICAgZG9uZQp9CgojIFJlbW92"\ "IiRwYXJlbnQiKQogICAgY2hpbGQ9JChiYXNlbmFtZSAiJHRvX3JlbW92ZSIpCiAgICBkb2NrZXIg"\
"ZXMgYSBwYXRoIHVzaW5nIGEgcm9vdCBEb2NrZXIgY29udGFpbmVyIChmb3IgcGVybWlzc2lvbnMp"\ "cnVuIC0tcm0gLXYgIiRhYnNfcGFyZW50IjovZGF0YSBhbHBpbmUgcm0gLXJmICIvZGF0YS8kY2hp"\
"Lgpfcm9vdF9yZW1vdmVfdHJlZSgpIHsKICAgIGxvY2FsIHRvX3JlbW92ZT0iJDEiCiAgICBwYXJl"\ "bGQiCn0KCgojIExvYWQgYXV0b2NvbW1hbmRzCnNvdXJjZSAiJHtBR0VOVF9QQVRIfS9kYXRhY29t"\
"bnQ9JChkaXJuYW1lICIkdG9fcmVtb3ZlIikKICAgIGFic19wYXJlbnQ9JChyZWFscGF0aCAiJHBh"\ "bWFuZHMuc2gi";
"cmVudCIpCiAgICBjaGlsZD0kKGJhc2VuYW1lICIkdG9fcmVtb3ZlIikKICAgIGRvY2tlciBydW4g"\
"LS1ybSAtdiAiJGFic19wYXJlbnQiOi9kYXRhIGFscGluZSBybSAtcmYgIi9kYXRhLyRjaGlsZCIK"\
"fQoKCiMgTG9hZCBhdXRvY29tbWFuZHMKc291cmNlICIke0FHRU5UX1BBVEh9L2RhdGFjb21tYW5k"\
"cy5zaCI=";
// Decode Base64 data // Decode Base64 data
size_t decoded_size = (strlen(filedata_base64) * 3) / 4; size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
@ -434,7 +433,7 @@ bool recreate_tree(std::string destination_folder) {
size_t actual_size; size_t actual_size;
base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &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); bool file_written = _recreate_file_(outpath, 6967493376886731479ULL, std::filesystem::perms(493), decoded_data, actual_size);
delete[] decoded_data; delete[] decoded_data;
any_written = any_written || file_written; any_written = any_written || file_written;
} }

View File

@ -0,0 +1,172 @@
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/output.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "command_registry.hpp"
#include "config.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "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;
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 = gConfig().get_local_backup_path();
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;
}
int backupdata_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)
{
// 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 auto &service : services)
okay &= backupdata_service(server, service.service_name);
return okay ? 0 : 1;
}
std::string service = safearg(ctx.args, 1);
return backupdata_service(server, service);
}
} // 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

@ -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 // Check if stdin is connected to a terminal if EDITOR is not set
editor_cmd = "nano -w " + quote(file_path); editor_cmd = "nano -w " + quote(file_path);
} else { } else {
std::cerr << "Error: Standard input is not a terminal and EDITOR environment variable is not set." << std::endl; 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; info << "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; info << "You can manually edit the file at: " << file_path << std::endl;
return false; return false;
} }
std::cout << "Editing file: " << file_path << std::endl; info << "Editing file: " << file_path << std::endl;
if (has_bb64) { if (has_bb64) {
return execute_local_command(editor_cmd, nullptr, cMode::Interactive); return execute_local_command("", editor_cmd, {}, nullptr, cMode::Interactive);
} }
else { else {
// might not have bb64 at this early stage. Direct edit. // 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); std::string config_file = localfile::server_json(server_name);
if (!edit_file(config_file, true)) { if (!edit_file(config_file, true)) {
std::cerr << "Error: Failed to edit server config" << std::endl; error << "Failed to edit server config" << std::endl;
std::cerr << "You can manually edit this file at: " << config_file << std::endl; info << "You can manually edit this file at: " << config_file << std::endl;
return 1; return 1;
} }
std::cout << "If you have changed DROPSHELL_DIR, you should manually move the files to the new location NOW." << std::endl info << "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 info << "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 << "Once moved, reinstall all services with: dropshell install " << server_name << std::endl;
return 0; 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); std::string config_file = localfile::service_env(server, service);
if (!std::filesystem::exists(config_file)) 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; return 1;
} }
if (edit_file(config_file, true) && std::filesystem::exists(config_file)) 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; return 0;
} }
@ -167,7 +167,7 @@ int edit_handler(const CommandContext& ctx) {
return 0; 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; return -1;
} }

View File

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

View File

@ -48,7 +48,7 @@ void help_autocomplete(const CommandContext& ctx) {
if (ctx.args.size() == 1) { if (ctx.args.size() == 1) {
// list all commands // list all commands
for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) { for (const auto& cmd : CommandRegistry::instance().list_primary_commands(false)) {
std::cout << cmd << std::endl; rawout << cmd << std::endl;
} }
} }
return; return;
@ -58,13 +58,13 @@ void show_command(const std::string& cmd) {
const auto& cmd_info = CommandRegistry::instance().find_command(cmd); const auto& cmd_info = CommandRegistry::instance().find_command(cmd);
if (!cmd_info) if (!cmd_info)
{ {
std::cout << "Unknown command: " << cmd << std::endl; error << "Unknown command: " << cmd << std::endl;
return; return;
} }
std::cout << " "; info << " ";
print_left_aligned(cmd_info->help_usage, 32); info << left_align(cmd_info->help_usage, 32);
std::cout << cmd_info->help_description << std::endl; info << cmd_info->help_description << std::endl;
} }
extern const std::string VERSION; extern const std::string VERSION;
@ -77,28 +77,26 @@ int show_command_help(const std::string& cmd) {
const auto& cmd_info = CommandRegistry::instance().find_command(cmd); const auto& cmd_info = CommandRegistry::instance().find_command(cmd);
if (!cmd_info) if (!cmd_info)
{ {
std::cout << "Unknown command: " << cmd << std::endl; error << "Unknown command: " << cmd << std::endl;
return 1; 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; info << 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;
std::cout << std::endl; info << " Equivalent names: ";
std::cout << " Equivalent names: ";
bool first = true; bool first = true;
for (const auto& name : cmd_info->names) { for (const auto& name : cmd_info->names) {
if (!first) std::cout << ", "; if (!first) info << ", ";
std::cout << name; info << name;
first = false; 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; return 0;
} }
@ -108,12 +106,12 @@ int help_handler(const CommandContext& ctx) {
if (ctx.args.size() > 0) if (ctx.args.size() > 0)
return show_command_help(ctx.args[0]); return show_command_help(ctx.args[0]);
std::cout << std::endl; info << std::endl;
maketitle("DropShell version " + VERSION); maketitle("DropShell version " + VERSION);
std::cout << std::endl; info << std::endl;
std::cout << "A tool for managing remote servers, by " << AUTHOR << std::endl; info << "A tool for managing remote servers, by " << AUTHOR << std::endl;
std::cout << std::endl; info << std::endl;
std::cout << "dropshell ..." << std::endl; info << "dropshell ..." << std::endl;
show_command("help"); show_command("help");
show_command("edit"); show_command("edit");
@ -129,60 +127,13 @@ int help_handler(const CommandContext& ctx) {
std::cout << std::endl; std::cout << std::endl;
show_command("start"); show_command("start");
show_command("stop"); show_command("stop");
std::cout << std::endl;
show_command("ssh");
std::cout << std::endl;
} }
return 0; 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 } // namespace dropshell

View File

@ -7,6 +7,7 @@
#include "utils/hash.hpp" #include "utils/hash.hpp"
#include "autogen/_agent.hpp" #include "autogen/_agent.hpp"
#include "services.hpp" #include "services.hpp"
#include "utils/output.hpp"
#include <unistd.h> #include <unistd.h>
#include <cstring> #include <cstring>
@ -100,7 +101,7 @@ namespace dropshell
} }
// Copy template files // 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; << 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), if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service),
server_env, silent)) server_env, silent))
@ -110,7 +111,7 @@ namespace dropshell
} }
// Copy service files // 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; << 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), if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service),
server_env, silent)) server_env, silent))
@ -121,12 +122,12 @@ namespace dropshell
// Run install script // Run install script
{ {
std::cout << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl; info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, silent, {}); server_env.run_remote_template_command(service, "install", {}, silent, {});
} }
// print health tick // print health tick
std::cout << "Health: " << shared_commands::healthtick(server, service) << std::endl; info << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true; return true;
} }
@ -281,30 +282,30 @@ namespace dropshell
int install_server(const std::string &server) int install_server(const std::string &server)
{ {
// install the dropshell agent on the given server. // install the dropshell agent on the given server.
std::cout << "Installing dropshell agent on " << server << std::endl; maketitle("Installing dropshell agent on " + server, sColour::INFO);
std::string agent_path = remotepath::agent(server); std::string agent_path = remotepath::agent(server);
if (agent_path.empty()) if (agent_path.empty())
{ {
std::cerr << "Failed to get agent path for " << server << std::endl; error << "Failed to get agent path for " << server << std::endl;
return 1; return 1;
} }
server_env_manager server_env(server); server_env_manager server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
std::cerr << "Invalid server environment for " << server << std::endl; error << "Invalid server environment for " << server << std::endl;
return 1; return 1;
} }
// now create the agent. // now create the agent.
// copy across from the local agent files. // copy across from the local agent files.
std::cout << "Copying local agent files to remote server... " << std::flush; info << "Copying local agent files to remote server... " << std::flush;
shared_commands::rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false); shared_commands::rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false);
std::cout << "done." << std::endl; info << "done." << std::endl;
// add in bb64. We can't use execute_remote_command() here, as that relies on bb64 which we're installing! // 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; info << "Installing bb64 on " << server << "..." << std::endl << std::flush;
std::string remote_cmd = std::string remote_cmd =
"ssh -p " + server_env.get_SSH_INFO().port + " " + server_env.get_SSH_INFO().user + "@" + server_env.get_SSH_INFO().host + "ssh -p " + server_env.get_SSH_INFO().port + " " + server_env.get_SSH_INFO().user + "@" + server_env.get_SSH_INFO().host +
@ -312,24 +313,20 @@ namespace dropshell
quote(agent_path) + " " + quote("$(id -u " + server_env.get_SSH_USER() + "):$(id -g " + server_env.get_SSH_USER() + ")") + "'"; 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; //std::cout << "Executing: " << remote_cmd << std::endl;
if (!execute_local_command(remote_cmd, nullptr, cMode::Silent)) if (!execute_local_command("", remote_cmd, {}, nullptr, cMode::Silent))
std::cerr << "Failed to download bb64 to " << agent_path << " on remote server." << std::endl; error << "Failed to download bb64 to " << agent_path << " on remote server." << std::endl;
else else
std::cout << "Downloaded bb64 to " << agent_path << " on remote server." << std::endl; debug << "Downloaded bb64 to " << agent_path << " on remote server." << std::endl;
// just test all is ok
// run the self-test. // run the self-test.
std::string output; bool okay = execute_ssh_command(server_env.get_SSH_INFO(), sCommand(agent_path, "./selftest.sh", {}), cMode::Defaults, nullptr);
bool okay = execute_ssh_command(server_env.get_SSH_INFO(), sCommand(agent_path, "./selftest.sh", {}), cMode::Defaults, &output);
if (!okay) if (!okay)
{ {
std::cerr << "ERROR: Failed to install remote agent on " << server << std::endl; error << "ERROR: Failed to install remote agent on " << server << std::endl;
std::cerr << "ERROR: Output: " << output << std::endl;
return 1; return 1;
} }
std::cout << output << std::endl; info << "Installation on " << server << " complete." << std::endl;
return 0; return 0;
} }
@ -345,7 +342,7 @@ namespace dropshell
if (!gConfig().is_config_set()) 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; return 1;
} }

View File

@ -63,7 +63,7 @@ int list_handler(const CommandContext& ctx) {
return 0; 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; return 0;
} }
@ -74,16 +74,16 @@ void list_servers() {
auto servers = get_configured_servers(); auto servers = get_configured_servers();
if (servers.empty()) { if (servers.empty()) {
std::cout << "No servers found" << std::endl; error << "No servers found" << std::endl;
std::cout << "Please run 'dropshell edit' to set up dropshell." << std::endl; info << "Please run 'dropshell edit' to set up dropshell." << std::endl;
std::cout << "Then run 'dropshell create-server' to create a server." << std::endl; info << "Then run 'dropshell create-server' to create a server." << std::endl;
return; return;
} }
tableprint tp("All DropShell Servers"); tableprint tp("All DropShell Servers");
tp.add_row({"Name", "User", "Address", "Health", "Ports"}); 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; int checked = 0;
transwarp::parallel exec{servers.size()}; transwarp::parallel exec{servers.size()};
@ -103,10 +103,10 @@ void list_servers() {
tp.add_row({server.name, server.ssh_user, server.ssh_host, serviceticks, ports_used_str}); tp.add_row({server.name, server.ssh_user, server.ssh_host, serviceticks, ports_used_str});
++checked; ++checked;
// print out a tick character for each server checked. // print out a tick character for each server checked.
std::cout << checked << "" << std::flush; info << checked << "" << std::flush;
}); });
task->wait(); task->wait();
std::cout << std::endl << std::endl; info << std::endl << std::endl;
tp.print(); tp.print();
} }
@ -116,7 +116,7 @@ void list_servers() {
void show_server_details(const std::string& server_name) { void show_server_details(const std::string& server_name) {
server_env_manager env(server_name); server_env_manager env(server_name);
if (!env.is_valid()) { 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; return;
} }
@ -126,14 +126,14 @@ void show_server_details(const std::string& server_name) {
std::string ssh_user = env.get_SSH_USER(); std::string ssh_user = env.get_SSH_USER();
std::string ssh_port = env.get_SSH_PORT(); std::string ssh_port = env.get_SSH_PORT();
if (!ssh_address.empty()) { if (!ssh_address.empty()) {
std::cout << std::endl << "Server Status:" << std::endl; info << std::endl << "Server Status:" << std::endl;
std::cout << std::string(40, '-') << std::endl; info << std::string(40, '-') << std::endl;
// Try to connect to the server // Try to connect to the server
std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " '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()); int result = system(cmd.c_str());
if (result == 0) { if (result == 0) {
std::cout << "Status: Online" << std::endl; info << "Status: Online" << std::endl;
// // Get uptime if possible // // Get uptime if possible
// cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null"; // cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null";
@ -142,10 +142,10 @@ void show_server_details(const std::string& server_name) {
// std::cout << "Error: Failed to get uptime" << std::endl; // std::cout << "Error: Failed to get uptime" << std::endl;
// } // }
} else { } else {
std::cout << "Status: Offline" << std::endl; warning << "Status: Offline" << std::endl;
} }
} }
std::cout << std::endl; info << std::endl;
//--------------------- //---------------------
{ {

View File

@ -57,7 +57,7 @@ int nuke_one(std::string server, std::string service)
service_info = get_service_info(server, service); service_info = get_service_info(server, service);
if (!SIvalid(service_info)) if (!SIvalid(service_info))
std::cerr << "Warning: Invalid service: " << service << std::endl; error << "Warning: Invalid service: " << service << std::endl;
if (server_env.check_remote_dir_exists(remotepath::service(server, service))) if (server_env.check_remote_dir_exists(remotepath::service(server, service)))
{ {
@ -65,47 +65,47 @@ int nuke_one(std::string server, std::string service)
// otherwise just uninstall. // otherwise just uninstall.
if (gTemplateManager().template_command_exists(service_info.template_name, "nuke")) if (gTemplateManager().template_command_exists(service_info.template_name, "nuke"))
{ {
std::cout << "Running nuke script for " << service << " on " << server << std::endl; info << "Running nuke script for " << service << " on " << server << std::endl;
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {})) if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
std::cerr << "Warning: Failed to run nuke script: " << service << std::endl; warning << "Failed to run nuke script: " << service << std::endl;
} }
else else
{ {
std::cout << "No nuke script found for " << service << " on " << server << std::endl; info << "No nuke script found for " << service << " on " << server << std::endl;
std::cout << "Running uninstall script instead and will clean directories." << std::endl; info << "Running uninstall script instead and will clean directories." << std::endl;
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {})) if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
std::cerr << "Warning: Failed to uninstall service: " << service << std::endl; warning << "Failed to uninstall service: " << service << std::endl;
} }
// Remove the service directory from the server, running in a docker container as root. // 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)) 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"); 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; info << "Remote service directory removed: " << remotepath::service(server, service) << std::endl;
} }
else else
std::cerr << "Warning: Failed to remove remote service directory" << std::endl; warning << "Failed to remove remote service directory" << std::endl;
} }
else else
std::cerr << "Warning: Service not found on remote server: " << remotepath::service(server, service) << std::endl; warning << "Service not found on remote server: " << remotepath::service(server, service) << std::endl;
} }
else else
std::cerr << "Warning: Can't nuke the remote service as the server is invalid: " << server << std::endl; warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory. // step 2 - nuke the local service directory.
std::string local_service_path = localpath::service(server, service); std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path)) if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{ {
std::cerr << "Warning: Local service directory not found: " << local_service_path << std::endl; warning << "Local service directory not found: " << local_service_path << std::endl;
} }
else else
{ {
auto itemsdeleted = std::filesystem::remove_all(local_service_path); auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0) if (itemsdeleted == 0)
std::cerr << "Error: Failed to remove local service directory" << std::endl; error << "Failed to remove local service directory" << std::endl;
} }
std::cout << "Nuked service " << service << " on server " << server << std::endl; info << "Nuked service " << service << " on server " << server << std::endl;
return 0; return 0;
} }
@ -126,7 +126,7 @@ int nuke_handler(const CommandContext &ctx)
std::string server_path = localpath::server(server); std::string server_path = localpath::server(server);
if (server_path.empty()) if (server_path.empty())
{ {
std::cerr << "Error: Server not found: " << server << std::endl; error << "Server not found: " << server << std::endl;
return 1; return 1;
} }

View File

@ -0,0 +1,137 @@
#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",
"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.
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;
int restoredata_handler(const CommandContext& ctx)
{
ASSERT(ctx.args.size() == 2, "Invalid number of arguments");
std::string server = ctx.args[0];
std::string service = ctx.args[1];
std::string backup_file = ctx.args[2];
server_env_manager server_env(server);
if (!server_env.is_valid()) {
error << "Server " << server << " is not valid" << std::endl;
return 1;
}
shared_commands::cBackupFileName backup_details(backup_file);
if (!backup_details.is_valid()) {
error << "Invalid backup file: " << backup_file << std::endl;
return 1;
}
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];
std::vector<shared_commands::cBackupFileName> backups;
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::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return;
}
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 (backup_details.get_template_name() == 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();
});
// 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 "directories.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp"
namespace dropshell namespace dropshell
{ {
@ -23,7 +24,7 @@ namespace dropshell
std::vector<ServerInfo> servers = get_configured_servers(); std::vector<ServerInfo> servers = get_configured_servers();
for (const auto &server : servers) for (const auto &server : servers)
{ {
std::cout << server.name << std::endl; rawout << server.name << std::endl;
} }
} }
else if (ctx.args.size() == 1) else if (ctx.args.size() == 1)
@ -32,7 +33,7 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]); std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]);
for (const auto &service : services) 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); std_autocomplete(ctx);
if (ctx.args.size() == 1) if (ctx.args.size() == 1)
std::cout << "all" << std::endl; rawout << "all" << std::endl;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -62,7 +63,7 @@ namespace dropshell
quote(local_path + "/") + " " + quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" +
remote_path + "/"); remote_path + "/");
return execute_local_command(rsync_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults)); return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -88,7 +89,7 @@ namespace dropshell
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10); std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10);
std::string mkdir_cmd = "mkdir -p " + quote(p); std::string mkdir_cmd = "mkdir -p " + quote(p);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) if (!execute_ssh_command(server_env.get_SSH_INFO(), 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 else
mPath = p; mPath = p;
} }
@ -114,12 +115,14 @@ namespace dropshell
server_env_manager env(server_name); server_env_manager env(server_name);
if (!env.is_valid()) if (!env.is_valid())
{ {
std::cerr << "Error: Invalid server environment" << std::endl; error << "Invalid server environment" << std::endl;
return status; return status;
} }
std::string output; 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::CaptureOutput | cMode::Silent,
&output))
return status; return status;
std::stringstream ss(output); std::stringstream ss(output);
@ -162,8 +165,6 @@ namespace dropshell
return status; return status;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// healthtick : SHARED COMMAND // healthtick : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -202,7 +203,6 @@ namespace dropshell
return ":error:"; return ":error:";
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// is_healthy : SHARED COMMAND // is_healthy : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -211,7 +211,7 @@ namespace dropshell
server_env_manager env(server); server_env_manager env(server);
if (!env.is_valid()) if (!env.is_valid())
{ {
std::cerr << "Error: Server service not initialized" << std::endl; error << "Server service not initialized" << std::endl;
return HealthStatus::ERROR; return HealthStatus::ERROR;
} }
@ -232,7 +232,6 @@ namespace dropshell
return HealthStatus::HEALTHY; return HealthStatus::HEALTHY;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// healthmark : SHARED COMMAND // healthmark : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -242,6 +241,91 @@ namespace dropshell
return HealthStatus2String(status); return HealthStatus2String(status);
} }
// ------------------------------------------------------------------------------------------------
// cBackupFileName : SHARED CLASS
// ------------------------------------------------------------------------------------------------
cBackupFileName::cBackupFileName(const std::string &server, const std::string &service, const std::string &template_name)
{
mServer = server;
mService = service;
mTemplateName = template_name;
// Get current datetime for backup filename
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::stringstream datetime;
datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
mDatetime = datetime.str();
}
cBackupFileName::cBackupFileName(const std::string &filename)
{
// Parse the filename according to the format:
// server + magic_string() + template_name + magic_string() + service + magic_string() + datetime + ".tgz"
std::string name = filename;
if (name.size() > 4 && name.substr(name.size() - 4) == ".tgz")
name = name.substr(0, name.size() - 4);
std::string sep = magic_string();
size_t first = name.find(sep);
size_t second = name.find(sep, first + sep.size());
size_t third = name.find(sep, second + sep.size());
if (first == std::string::npos || second == std::string::npos || third == std::string::npos)
{
mServer = mService = mTemplateName = mDatetime = "";
return;
}
mServer = name.substr(0, first);
mTemplateName = name.substr(first + sep.size(), second - (first + sep.size()));
mService = name.substr(second + sep.size(), third - (second + sep.size()));
mDatetime = name.substr(third + sep.size());
}
std::string cBackupFileName::get_filename() const
{
return mServer + magic_string() + mTemplateName + magic_string() + mService + magic_string() + mDatetime + ".tgz";
}
std::string cBackupFileName::get_server() const { return mServer; }
std::string cBackupFileName::get_service() const { return mService; }
std::string cBackupFileName::get_template_name() const { return mTemplateName; }
std::string cBackupFileName::get_datetime() const { return mDatetime; }
bool cBackupFileName::is_valid() const
{
// All fields must be non-empty, and none may contain the magic string
return !mServer.empty() && !mService.empty() && !mTemplateName.empty() && !mDatetime.empty() &&
!has_magic_string(mServer) && !has_magic_string(mService) && !has_magic_string(mTemplateName);
}
// ------------------------------------------------------------------------------------------------
// scp_file_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool scp_file_to_remote(const 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 shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -57,6 +57,29 @@ namespace dropshell
void std_autocomplete(const CommandContext &ctx); void std_autocomplete(const CommandContext &ctx);
void std_autocomplete_allowall(const CommandContext &ctx); void std_autocomplete_allowall(const CommandContext &ctx);
class cBackupFileName
{
public:
cBackupFileName(const std::string &server, const std::string &service, const std::string &template_name);
cBackupFileName(const std::string &filename);
std::string get_filename() const;
std::string get_server() const;
std::string get_service() const;
std::string get_template_name() const;
std::string get_datetime() const;
bool is_valid() const;
private:
std::string mServer;
std::string mService;
std::string mTemplateName;
std::string mDatetime;
};
bool scp_file_to_remote(const 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);
} // namespace shared_commands } // namespace shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -6,6 +6,7 @@
#include "server_env_manager.hpp" #include "server_env_manager.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "templates.hpp"
namespace dropshell namespace dropshell
{ {
@ -44,7 +45,7 @@ namespace dropshell
server_env_manager server_env(server); server_env_manager server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
std::cerr << "Error: Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; return false;
} }
execute_ssh_command(server_env.get_SSH_INFO(), sCommand(remotepath::DROPSHELL_DIR(server), "ls --color && bash", {}), cMode::Interactive); execute_ssh_command(server_env.get_SSH_INFO(), sCommand(remotepath::DROPSHELL_DIR(server), "ls --color && bash", {}), cMode::Interactive);
@ -53,6 +54,33 @@ namespace dropshell
bool ssh_into_service(const std::string &server, const std::string &service) bool ssh_into_service(const std::string &server, const std::string &service)
{ {
server_env_manager server_env(server);
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; return true;
} }
@ -60,7 +88,7 @@ namespace dropshell
{ {
if (ctx.args.size() < 1) if (ctx.args.size() < 1)
{ {
std::cerr << "Error: Server name is required" << std::endl; error << "Server name is required" << std::endl;
return 1; return 1;
} }

View File

@ -6,6 +6,7 @@
#include "server_env_manager.hpp" #include "server_env_manager.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp"
namespace dropshell namespace dropshell
{ {
@ -46,7 +47,7 @@ namespace dropshell
server_env_manager server_env(server); server_env_manager server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
std::cerr << "Error: Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; return false;
} }
@ -55,10 +56,10 @@ namespace dropshell
if (started) if (started)
{ {
std::cout << "Service " << service << " on server " << server << " started." << std::endl; info << "Service " << service << " on server " << server << " started." << std::endl;
return true; return true;
} }
std::cerr << "Error: Failed to start service " << service << " on server " << server << std::endl; error << "Failed to start service " << service << " on server " << server << std::endl;
return false; return false;
} }

View File

@ -6,6 +6,7 @@
#include "server_env_manager.hpp" #include "server_env_manager.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp"
namespace dropshell namespace dropshell
{ {
@ -46,7 +47,7 @@ namespace dropshell
server_env_manager server_env(server); server_env_manager server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
std::cerr << "Error: Server " << server << " is not valid" << std::endl; error << "Server " << server << " is not valid" << std::endl;
return false; return false;
} }
@ -55,10 +56,10 @@ namespace dropshell
if (stopped) if (stopped)
{ {
std::cout << "Service " << service << " on server " << server << " stopped." << std::endl; info << "Service " << service << " on server " << server << " stopped." << std::endl;
return true; return true;
} }
std::cerr << "Error: Failed to stop service " << service << " on server " << server << std::endl; error << "Failed to stop service " << service << " on server " << server << std::endl;
return false; return false;
} }
@ -66,7 +67,7 @@ namespace dropshell
{ {
if (ctx.args.size() < 2) if (ctx.args.size() < 2)
{ {
std::cerr << "Error: Server name and service name are both required" << std::endl; error << "Server name and service name are both required" << std::endl;
return 1; return 1;
} }

View File

@ -49,14 +49,14 @@ namespace dropshell
server_env_manager server_env(server); server_env_manager server_env(server);
if (!server_env.is_valid()) if (!server_env.is_valid())
{ {
std::cerr << "Invalid server: " << server << std::endl; error << "Invalid server: " << server << std::endl;
return false; // should never hit this. return false; // should never hit this.
} }
// 2. Check if service directory exists on server // 2. Check if service directory exists on server
if (!server_env.check_remote_dir_exists(remotepath::service(server, service))) if (!server_env.check_remote_dir_exists(remotepath::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 return true; // Nothing to uninstall
} }
@ -64,20 +64,20 @@ namespace dropshell
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh"; std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, silent, {})) if (!server_env.run_remote_template_command(service, "uninstall", {}, silent, {}))
if (!silent) if (!silent)
std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl; warning << "Uninstall script failed, but continuing with directory removal" << std::endl;
// 4. Remove the service directory from the server, running in a docker container as root. // 4. Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), silent)) if (server_env.remove_remote_dir(remotepath::service(server, service), silent))
{ {
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall"); ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
if (!silent) 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) else if (!silent)
std::cerr << "Warning: Failed to remove remote service directory" << std::endl; warning << "Failed to remove remote service directory" << std::endl;
if (!silent) if (!silent)
std::cout << "Completed service " << service << " uninstall on " << server << std::endl; info << "Completed service " << service << " uninstall on " << server << std::endl;
return true; return true;
} }
@ -85,7 +85,7 @@ namespace dropshell
{ {
if (ctx.args.size() < 1) 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; return 1;
} }

View File

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

View File

@ -91,7 +91,6 @@ 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) : 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) mServerEnv(server_name), mServer(server_name), mService(service_name), mValid(false)
@ -147,7 +146,7 @@ bool service_runner::fullnuke()
} }
std::string rm_cmd = "rm -rf " + quote(local_service_path); std::string rm_cmd = "rm -rf " + quote(local_service_path);
if (!execute_local_command(rm_cmd, nullptr, cMode::Silent)) { if (!execute_local_command("", rm_cmd, {}, nullptr, cMode::Silent)) {
std::cerr << "Failed to remove service directory" << std::endl; std::cerr << "Failed to remove service directory" << std::endl;
return false; return false;
} }
@ -267,13 +266,13 @@ bool service_runner::interactive_ssh_service()
bool service_runner::scp_file_to_remote(const std::string &local_path, const std::string &remote_path, bool silent) 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" : ""); 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)); 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) 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" : ""); 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)); return execute_local_command("", scp_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
} }
bool service_runner::restore(std::string backup_file, bool silent) bool service_runner::restore(std::string backup_file, bool silent)
@ -298,7 +297,7 @@ bool service_runner::restore(std::string backup_file, bool silent)
} }
// split the backup filename into parts based on the magic string // split the backup filename into parts based on the magic string
std::vector<std::string> parts = dropshell::split(backup_file, magic_string); std::vector<std::string> parts = dropshell::split(backup_file, "-_-");
if (parts.size() != 4) { if (parts.size() != 4) {
std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl; std::cerr << "Error: Backup file format is incompatible, - in one of the names?" << std::endl;
return false; return false;
@ -383,12 +382,6 @@ bool service_runner::restore(std::string backup_file, bool silent)
} }
bool name_breaks_backups(std::string name)
{
// if name contains -_-, return true
return name.find("-_-") != std::string::npos;
}
// backup the service over ssh, using the credentials from server.env (via server_env.hpp) // backup the service over ssh, using the credentials from server.env (via server_env.hpp)
// 1. run backup.sh on the server // 1. run backup.sh on the server
// 2. create a backup file with format server-service-datetime.tgz // 2. create a backup file with format server-service-datetime.tgz
@ -452,17 +445,18 @@ bool service_runner::backup(bool silent) {
std::stringstream datetime; std::stringstream datetime;
datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S"); datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
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;}
// Construct backup filename // Construct backup filename
std::string backup_filename = mServer + magic_string + service_info.template_name + magic_string + mService + magic_string + datetime.str() + ".tgz"; 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 remote_backup_file_path = remote_backups_dir + "/" + backup_filename;
std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_filename).string(); 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 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"); ASSERT(3 == count_substring("-_-", local_backup_file_path), "Invalid backup filename");
{ // Run backup script { // Run backup script
shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv); shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv);
@ -501,7 +495,7 @@ std::string service_runner::get_latest_backup_file(const std::string& server, co
} }
// Build the expected prefix for backup files // Build the expected prefix for backup files
std::string prefix = server + magic_string + info.template_name + magic_string + service + magic_string; std::string prefix = server + "-_-" + info.template_name + "-_-" + service + "-_-";
std::string latest_file; std::string latest_file;
std::string latest_datetime; std::string latest_datetime;

View File

@ -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["SERVICE"] = service_name;
all_env_vars["AGENT_PATH"] = remotepath::agent(server_name); all_env_vars["AGENT_PATH"] = remotepath::agent(server_name);
all_env_vars["HOST_NAME"] = server_info.ssh_host; all_env_vars["HOST_NAME"] = server_info.ssh_host;
all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.
// Lambda function to load environment variables from a file // Lambda function to load environment variables from a file
auto load_env_file = [&all_env_vars](const std::string& file) { auto load_env_file = [&all_env_vars](const std::string& file) {

View File

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

View File

@ -13,6 +13,7 @@
#include "utils/b64ed.hpp" #include "utils/b64ed.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "utils/output.hpp"
namespace dropshell namespace dropshell
{ {
@ -21,6 +22,10 @@ namespace dropshell
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute. return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
} }
// ----------------------------------------------------------------------------------------------------------
// execute_local_command_interactive
// ----------------------------------------------------------------------------------------------------------
bool execute_local_command_interactive(const sCommand &command) bool execute_local_command_interactive(const sCommand &command)
{ {
if (command.get_command_to_run().empty()) if (command.get_command_to_run().empty())
@ -51,30 +56,59 @@ 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"); public:
if (command.get_command_to_run().empty()) fancypinter(sColour startColour) : startColour_(startColour), currentColour_(startColour) {}
return false;
std::string full_cmd = command.construct_cmd(localpath::agent()) + " 2>&1"; void print_chunk(std::string chunk)
FILE *pipe = popen(full_cmd.c_str(), "r");
if (!pipe)
{ {
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]; colourstream(currentColour_) << chunk;
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) newline_ = (chunk[chunk.size()-1] == '\n');
{
(*output) += buffer;
}
int ret = pclose(pipe);
return EXITSTATUSCHECK(ret);
} }
bool execute_local_command(std::string command, std::string *output, cMode mode) void print(const std::string& buffer) {
{ size_t start = 0;
return execute_local_command("", command, {}, output, mode); 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) 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)
{ {
@ -88,29 +122,36 @@ namespace dropshell
return execute_local_command_interactive(command); 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()) if (command.get_command_to_run().empty())
return false; return false;
bool silent = hasFlag(mode, cMode::Silent); bool silent = hasFlag(mode, cMode::Silent);
std::string full_cmd = command.construct_cmd(localpath::agent()) + " 2>&1" + (silent ? " > /dev/null" : "");
int ret = system(full_cmd.c_str());
bool ok = EXITSTATUSCHECK(ret); std::string full_cmd = command.construct_cmd(localpath::agent()) + (hasFlag(mode, cMode::CaptureOutput) ? " 2>&1" : ""); // capture both stdout and stderr
if (!ok && !silent)
FILE *pipe = popen(full_cmd.c_str(), "r");
if (!pipe)
{ {
std::cerr << "Error: Failed to execute command: " << std::endl; return false;
std::cerr << full_cmd << std::endl;
} }
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) 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()) if (remote_command.get_command_to_run().empty())
@ -144,6 +185,9 @@ namespace dropshell
return rval; return rval;
} }
// ----------------------------------------------------------------------------------------------------------
// makesafecmd
// ----------------------------------------------------------------------------------------------------------
std::string sCommand::makesafecmd(std::string agent_path, const std::string &command) const std::string sCommand::makesafecmd(std::string agent_path, const std::string &command) const
{ {
if (command.empty()) if (command.empty())
@ -153,6 +197,9 @@ namespace dropshell
return commandstr; return commandstr;
} }
// ----------------------------------------------------------------------------------------------------------
// construct_cmd
// ----------------------------------------------------------------------------------------------------------
std::string sCommand::construct_cmd(std::string agent_path) const std::string sCommand::construct_cmd(std::string agent_path) const
{ {
if (mCmd.empty()) if (mCmd.empty())

View File

@ -31,7 +31,6 @@ typedef struct sSSHInfo {
std::string server_ID; // dropshell name for server. std::string server_ID; // dropshell name for server.
} sSSHInfo; } 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_local_command(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars, std::string * output = nullptr, cMode mode = cMode::Defaults);
bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_command, cMode mode = cMode::Defaults, std::string * output = nullptr); bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & remote_command, cMode mode = cMode::Defaults, std::string * output = nullptr);

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

@ -10,10 +10,20 @@
namespace dropshell { namespace dropshell {
void maketitle(const std::string& title) { std::string magic_string() {
std::cout << std::string(title.length() + 4, '-') << std::endl; return "-_-";
std::cout << "| " << title << " |" << std::endl; }
std::cout << std::string(title.length() + 4, '-') << std::endl;
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) { bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line) {
@ -334,20 +344,6 @@ std::string safearg(int argc, char *argv[], int index)
return argv[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) { std::string left_align(const std::string & str, int width) {
if (static_cast<int>(str.size()) >= width) if (static_cast<int>(str.size()) >= width)
return str; return str;

View File

@ -3,6 +3,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "output.hpp"
namespace dropshell { namespace dropshell {
/** /**
@ -10,10 +12,12 @@ namespace dropshell {
* *
* @param title The title string to display * @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); bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line);
std::string magic_string();
bool has_magic_string(std::string name);
// utility functions // utility functions
std::string trim(std::string str); std::string trim(std::string str);
@ -45,10 +49,6 @@ int die(const std::string & msg);
std::string safearg(int argc, char *argv[], int index); std::string safearg(int argc, char *argv[], int index);
std::string safearg(const std::vector<std::string> & args, 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 left_align(const std::string & str, int width);
std::string right_align(const std::string & str, int width); std::string right_align(const std::string & str, int width);
std::string center_align(const std::string & str, int width); std::string center_align(const std::string & str, int width);