Compare commits

..

12 Commits

Author SHA1 Message Date
548ffea6f9 dropshell release 2025.0518.2308
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 23:08:44 +12:00
434a2bc6da dropshell release 2025.0518.2245
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 22:45:25 +12:00
cf8738aee9 dropshell release 2025.0518.2236
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 22:37:00 +12:00
d80820db15 Back to issue from pre-refactor!
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 20:23:15 +12:00
fb6974b51a Working on backup/restore.
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 20:19:47 +12:00
263edd9b50 dropshell release 2025.0518.1558
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 15:58:29 +12:00
e45afe460b Backups seem to be working
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
.
2025-05-18 15:43:19 +12:00
630a9fd19a Add backupdata
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 15:41:51 +12:00
f89d90c12b dropshell release 2025.0518.1451
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 14:51:53 +12:00
5883c62c54 Tidy output
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 14:29:51 +12:00
b278e81533 info for table print
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 14:03:12 +12:00
6c99b429b9 dropshell release 2025.0518.1356
Some checks failed
Dropshell Test / Build_and_Test (push) Has been cancelled
2025-05-18 13:56:52 +12:00
28 changed files with 1371 additions and 793 deletions

View File

@ -47,7 +47,7 @@ _autocommandrun_path() {
local path_parent=$(dirname ${path}) local path_parent=$(dirname ${path})
local path_child=$(basename ${path}) local path_child=$(basename ${path})
if [ -d "${path_parent}/${path_child}" ]; then if [ -d "${path_parent}/${path_child}" ]; then
docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rf /volume/${path_child}" || echo "Failed to nuke path ${path}" docker run --rm -v ${path_parent}:/volume debian bash -c "rm -rfv /volume/${path_child}" || echo "Failed to nuke path ${path}"
else else
echo "Path ${path} does not exist - nothing to nuke" echo "Path ${path} does not exist - nothing to nuke"
fi fi
@ -61,8 +61,14 @@ _autocommandrun_path() {
fi fi
;; ;;
restore) restore)
echo "Restoring path ${path}" if [ ! -f "${backup_folder}/backup.tgz" ]; then
tar -xzvf ${backup_folder}/backup.tgz -C ${path} --strip-components=1 echo "Backup file ${backup_folder}/backup.tgz does not exist - nothing to restore"
else
echo "Clearing existing data in path ${path}"
docker run --rm -v ${path}:/path debian bash -c "rm -rfv /path/{*,.*}"
echo "Restoring path ${path} from backup file ${backup_folder}/backup.tgz"
tar -xzvf ${backup_folder}/backup.tgz -C ${path} --strip-components=1
fi
;; ;;
esac esac
} }
@ -91,7 +97,8 @@ _autocommandrun_file() {
restore) restore)
echo "Restoring file ${filepath}" echo "Restoring file ${filepath}"
local file_name=$(basename ${filepath}) local file_name=$(basename ${filepath})
cp ${backup_folder}/${file_name} ${filepath} rm -f ${filepath} || die "Unable to remove existing file ${filepath}, restore failed."
cp ${backup_folder}/${file_name} ${filepath} || die "Unable to copy file ${backup_folder}/${file_name} to ${filepath}, restore failed."
;; ;;
esac esac
} }

View File

@ -152,81 +152,91 @@ bool recreate_tree(std::string destination_folder) {
"ICAgbG9jYWwgcGF0aF9jaGlsZD0kKGJhc2VuYW1lICR7cGF0aH0pCiAgICAgICAgICAgIGlmIFsg"\ "ICAgbG9jYWwgcGF0aF9jaGlsZD0kKGJhc2VuYW1lICR7cGF0aH0pCiAgICAgICAgICAgIGlmIFsg"\
"LWQgIiR7cGF0aF9wYXJlbnR9LyR7cGF0aF9jaGlsZH0iIF07IHRoZW4KICAgICAgICAgICAgICAg"\ "LWQgIiR7cGF0aF9wYXJlbnR9LyR7cGF0aF9jaGlsZH0iIF07IHRoZW4KICAgICAgICAgICAgICAg"\
"IGRvY2tlciBydW4gLS1ybSAtdiAke3BhdGhfcGFyZW50fTovdm9sdW1lIGRlYmlhbiBiYXNoIC1j"\ "IGRvY2tlciBydW4gLS1ybSAtdiAke3BhdGhfcGFyZW50fTovdm9sdW1lIGRlYmlhbiBiYXNoIC1j"\
"ICJybSAtcmYgL3ZvbHVtZS8ke3BhdGhfY2hpbGR9IiB8fCBlY2hvICJGYWlsZWQgdG8gbnVrZSBw"\ "ICJybSAtcmZ2IC92b2x1bWUvJHtwYXRoX2NoaWxkfSIgfHwgZWNobyAiRmFpbGVkIHRvIG51a2Ug"\
"YXRoICR7cGF0aH0iCiAgICAgICAgICAgIGVsc2UKICAgICAgICAgICAgICAgIGVjaG8gIlBhdGgg"\ "cGF0aCAke3BhdGh9IgogICAgICAgICAgICBlbHNlCiAgICAgICAgICAgICAgICBlY2hvICJQYXRo"\
"JHtwYXRofSBkb2VzIG5vdCBleGlzdCAtIG5vdGhpbmcgdG8gbnVrZSIKICAgICAgICAgICAgZmkK"\ "ICR7cGF0aH0gZG9lcyBub3QgZXhpc3QgLSBub3RoaW5nIHRvIG51a2UiCiAgICAgICAgICAgIGZp"\
"ICAgICAgICAgICAgOzsKICAgICAgICBiYWNrdXApCiAgICAgICAgICAgIGVjaG8gIkJhY2tpbmcg"\ "CiAgICAgICAgICAgIDs7CiAgICAgICAgYmFja3VwKQogICAgICAgICAgICBlY2hvICJCYWNraW5n"\
"dXAgcGF0aCAke3BhdGh9IgogICAgICAgICAgICBpZiBbIC1kICIke3BhdGh9IiBdOyB0aGVuCiAg"\ "IHVwIHBhdGggJHtwYXRofSIKICAgICAgICAgICAgaWYgWyAtZCAiJHtwYXRofSIgXTsgdGhlbgog"\
"ICAgICAgICAgICAgICBkb2NrZXIgcnVuIC0tcm0gLXYgJHtwYXRofTovcGF0aCAtdiAke2JhY2t1"\ "ICAgICAgICAgICAgICAgZG9ja2VyIHJ1biAtLXJtIC12ICR7cGF0aH06L3BhdGggLXYgJHtiYWNr"\
"cF9mb2xkZXJ9Oi9iYWNrdXAgZGViaWFuIGJhc2ggLWMgInRhciAtY3p2ZiAvYmFja3VwL2JhY2t1"\ "dXBfZm9sZGVyfTovYmFja3VwIGRlYmlhbiBiYXNoIC1jICJ0YXIgLWN6dmYgL2JhY2t1cC9iYWNr"\
"cC50Z3ogLUMgL3BhdGggLiAmJiBjaG93biAtUiAkTVlJRDokTVlHUlAgL2JhY2t1cCIKICAgICAg"\ "dXAudGd6IC1DIC9wYXRoIC4gJiYgY2hvd24gLVIgJE1ZSUQ6JE1ZR1JQIC9iYWNrdXAiCiAgICAg"\
"ICAgICAgZWxzZQogICAgICAgICAgICAgICAgZWNobyAiUGF0aCAke3BhdGh9IGRvZXMgbm90IGV4"\ "ICAgICAgIGVsc2UKICAgICAgICAgICAgICAgIGVjaG8gIlBhdGggJHtwYXRofSBkb2VzIG5vdCBl"\
"aXN0IC0gbm90aGluZyB0byBiYWNrdXAiCiAgICAgICAgICAgIGZpCiAgICAgICAgICAgIDs7CiAg"\ "eGlzdCAtIG5vdGhpbmcgdG8gYmFja3VwIgogICAgICAgICAgICBmaQogICAgICAgICAgICA7Owog"\
"ICAgICAgcmVzdG9yZSkKICAgICAgICAgICAgZWNobyAiUmVzdG9yaW5nIHBhdGggJHtwYXRofSIK"\ "ICAgICAgIHJlc3RvcmUpCiAgICAgICAgICAgIGlmIFsgISAtZiAiJHtiYWNrdXBfZm9sZGVyfS9i"\
"ICAgICAgICAgICAgdGFyIC14enZmICR7YmFja3VwX2ZvbGRlcn0vYmFja3VwLnRneiAtQyAke3Bh"\ "YWNrdXAudGd6IiBdOyB0aGVuCiAgICAgICAgICAgICAgICBlY2hvICJCYWNrdXAgZmlsZSAke2Jh"\
"dGh9IC0tc3RyaXAtY29tcG9uZW50cz0xCiAgICAgICAgICAgIDs7CiAgICBlc2FjCn0KCl9hdXRv"\ "Y2t1cF9mb2xkZXJ9L2JhY2t1cC50Z3ogZG9lcyBub3QgZXhpc3QgLSBub3RoaW5nIHRvIHJlc3Rv"\
"Y29tbWFuZHJ1bl9maWxlKCkgewogICAgbG9jYWwgY29tbWFuZD0iJDEiCiAgICBsb2NhbCBmaWxl"\ "cmUiCiAgICAgICAgICAgIGVsc2UKICAgICAgICAgICAgICAgIGVjaG8gIkNsZWFyaW5nIGV4aXN0"\
"cGF0aD0iJDIiCiAgICBsb2NhbCBiYWNrdXBfZm9sZGVyPSIkMyIKCiAgICBjYXNlICIkY29tbWFu"\ "aW5nIGRhdGEgaW4gcGF0aCAke3BhdGh9IgogICAgICAgICAgICAgICAgZG9ja2VyIHJ1biAtLXJt"\
"ZCIgaW4KICAgICAgICBjcmVhdGUpCiAgICAgICAgICAgIDs7CiAgICAgICAgbnVrZSkKICAgICAg"\ "IC12ICR7cGF0aH06L3BhdGggZGViaWFuIGJhc2ggLWMgInJtIC1yZnYgL3BhdGgveyosLip9Igog"\
"ICAgICAgcm0gLWYgJHtmaWxlcGF0aH0KICAgICAgICAgICAgOzsKICAgICAgICBiYWNrdXApCiAg"\ "ICAgICAgICAgICAgICAgZWNobyAiUmVzdG9yaW5nIHBhdGggJHtwYXRofSBmcm9tIGJhY2t1cCBm"\
"ICAgICAgICAgIGVjaG8gIkJhY2tpbmcgdXAgZmlsZSAke2ZpbGVwYXRofSIKICAgICAgICAgICAg"\ "aWxlICR7YmFja3VwX2ZvbGRlcn0vYmFja3VwLnRneiIKICAgICAgICAgICAgICAgIHRhciAteHp2"\
"bG9jYWwgZmlsZV9wYXJlbnQ9JChkaXJuYW1lICR7ZmlsZXBhdGh9KQogICAgICAgICAgICBsb2Nh"\ "ZiAke2JhY2t1cF9mb2xkZXJ9L2JhY2t1cC50Z3ogLUMgJHtwYXRofSAtLXN0cmlwLWNvbXBvbmVu"\
"bCBmaWxlX25hbWU9JChiYXNlbmFtZSAke2ZpbGVwYXRofSkKICAgICAgICAgICAgaWYgWyAtZiAi"\ "dHM9MQogICAgICAgICAgICBmaQogICAgICAgICAgICA7OwogICAgZXNhYwp9CgpfYXV0b2NvbW1h"\
"JHtmaWxlX3BhcmVudH0vJHtmaWxlX25hbWV9IiBdOyB0aGVuCiAgICAgICAgICAgICAgICBkb2Nr"\ "bmRydW5fZmlsZSgpIHsKICAgIGxvY2FsIGNvbW1hbmQ9IiQxIgogICAgbG9jYWwgZmlsZXBhdGg9"\
"ZXIgcnVuIC0tcm0gLXYgJHtmaWxlX3BhcmVudH06L3ZvbHVtZSAtdiAke2JhY2t1cF9mb2xkZXJ9"\ "IiQyIgogICAgbG9jYWwgYmFja3VwX2ZvbGRlcj0iJDMiCgogICAgY2FzZSAiJGNvbW1hbmQiIGlu"\
"Oi9iYWNrdXAgZGViaWFuIGJhc2ggLWMgImNwIC92b2x1bWUvJHtmaWxlX25hbWV9IC9iYWNrdXAv"\ "CiAgICAgICAgY3JlYXRlKQogICAgICAgICAgICA7OwogICAgICAgIG51a2UpCiAgICAgICAgICAg"\
"JHtmaWxlX25hbWV9ICYmIGNob3duIC1SICRNWUlEOiRNWUdSUCAvYmFja3VwIgogICAgICAgICAg"\ "IHJtIC1mICR7ZmlsZXBhdGh9CiAgICAgICAgICAgIDs7CiAgICAgICAgYmFja3VwKQogICAgICAg"\
"ICBlbHNlCiAgICAgICAgICAgICAgICBlY2hvICJGaWxlICR7ZmlsZXBhdGh9IGRvZXMgbm90IGV4"\ "ICAgICBlY2hvICJCYWNraW5nIHVwIGZpbGUgJHtmaWxlcGF0aH0iCiAgICAgICAgICAgIGxvY2Fs"\
"aXN0IC0gbm90aGluZyB0byBiYWNrdXAiCiAgICAgICAgICAgIGZpCiAgICAgICAgICAgIDs7CiAg"\ "IGZpbGVfcGFyZW50PSQoZGlybmFtZSAke2ZpbGVwYXRofSkKICAgICAgICAgICAgbG9jYWwgZmls"\
"ICAgICAgcmVzdG9yZSkKICAgICAgICAgICAgZWNobyAiUmVzdG9yaW5nIGZpbGUgJHtmaWxlcGF0"\ "ZV9uYW1lPSQoYmFzZW5hbWUgJHtmaWxlcGF0aH0pCiAgICAgICAgICAgIGlmIFsgLWYgIiR7Zmls"\
"aH0iCiAgICAgICAgICAgIGxvY2FsIGZpbGVfbmFtZT0kKGJhc2VuYW1lICR7ZmlsZXBhdGh9KQog"\ "ZV9wYXJlbnR9LyR7ZmlsZV9uYW1lfSIgXTsgdGhlbgogICAgICAgICAgICAgICAgZG9ja2VyIHJ1"\
"ICAgICAgICAgICBjcCAke2JhY2t1cF9mb2xkZXJ9LyR7ZmlsZV9uYW1lfSAke2ZpbGVwYXRofQog"\ "biAtLXJtIC12ICR7ZmlsZV9wYXJlbnR9Oi92b2x1bWUgLXYgJHtiYWNrdXBfZm9sZGVyfTovYmFj"\
"ICAgICAgICAgICA7OwogICAgZXNhYwp9CgpfYXV0b2NvbW1hbmRwYXJzZSgpIHsKICAgICMgZmly"\ "a3VwIGRlYmlhbiBiYXNoIC1jICJjcCAvdm9sdW1lLyR7ZmlsZV9uYW1lfSAvYmFja3VwLyR7Zmls"\
"c3QgYXJndW1lbnQgaXMgdGhlIGNvbW1hbmQKICAgICMgaWYgdGhlIGNvbW1hbmQgaXMgYmFja3Vw"\ "ZV9uYW1lfSAmJiBjaG93biAtUiAkTVlJRDokTVlHUlAgL2JhY2t1cCIKICAgICAgICAgICAgZWxz"\
"IG9yIHJlc3RvcmUsIHRoZW4gdGhlIGxhc3QgdHdvIGFyZ3VtZW50cyBhcmUgdGhlIGJhY2t1cCBm"\ "ZQogICAgICAgICAgICAgICAgZWNobyAiRmlsZSAke2ZpbGVwYXRofSBkb2VzIG5vdCBleGlzdCAt"\
"aWxlIGFuZCB0aGUgdGVtcG9yYXJ5IHBhdGgKICAgICMgYWxsIG90aGVyIGFyZ3VtZW50cyBhcmUg"\ "IG5vdGhpbmcgdG8gYmFja3VwIgogICAgICAgICAgICBmaQogICAgICAgICAgICA7OwogICAgICAg"\
"b2YgZm9ybToKICAgICMga2V5PXZhbHVlCiAgICAjIHdoZXJlIGtleSBjYW4gYmUgb25lIG9mIHZv"\ "IHJlc3RvcmUpCiAgICAgICAgICAgIGVjaG8gIlJlc3RvcmluZyBmaWxlICR7ZmlsZXBhdGh9Igog"\
"bHVtZSwgcGF0aCBvciBmaWxlLgogICAgIyB2YWx1ZSBpcyB0aGUgcGF0aCBvciB2b2x1bWUgbmFt"\ "ICAgICAgICAgICBsb2NhbCBmaWxlX25hbWU9JChiYXNlbmFtZSAke2ZpbGVwYXRofSkKICAgICAg"\
"ZS4KCiAgICAjIHdlIGl0ZXJhdGUgb3ZlciB0aGUga2V5PXZhbHVlIGFyZ3VtZW50cywgYW5kIGZv"\ "ICAgICAgcm0gLWYgJHtmaWxlcGF0aH0gfHwgZGllICJVbmFibGUgdG8gcmVtb3ZlIGV4aXN0aW5n"\
"ciBlYWNoIHdlIGNhbGw6CiAgICAjICAgIGF1dG9ydW4gPGNvbW1hbmQ+IDxiYWNrdXBmaWxlPiA8"\ "IGZpbGUgJHtmaWxlcGF0aH0sIHJlc3RvcmUgZmFpbGVkLiIKICAgICAgICAgICAgY3AgJHtiYWNr"\
"a2V5PiA8dmFsdWU+CgogICAgbG9jYWwgY29tbWFuZD0iJDEiCiAgICBzaGlmdAoKICAgIGxvY2Fs"\ "dXBfZm9sZGVyfS8ke2ZpbGVfbmFtZX0gJHtmaWxlcGF0aH0gfHwgZGllICJVbmFibGUgdG8gY29w"\
"IGJhY2t1cF90ZW1wX3BhdGg9IiQxIgogICAgc2hpZnQKCiAgICBlY2hvICJhdXRvY29tbWFuZHBh"\ "eSBmaWxlICR7YmFja3VwX2ZvbGRlcn0vJHtmaWxlX25hbWV9IHRvICR7ZmlsZXBhdGh9LCByZXN0"\
"cnNlOiBjb21tYW5kPSRjb21tYW5kIGJhY2t1cF90ZW1wX3BhdGg9JGJhY2t1cF90ZW1wX3BhdGgi"\ "b3JlIGZhaWxlZC4iCiAgICAgICAgICAgIDs7CiAgICBlc2FjCn0KCl9hdXRvY29tbWFuZHBhcnNl"\
"CgogICAgIyBFeHRyYWN0IHRoZSBiYWNrdXAgZmlsZSBhbmQgdGVtcCBwYXRoIChsYXN0IHR3byBh"\ "KCkgewogICAgIyBmaXJzdCBhcmd1bWVudCBpcyB0aGUgY29tbWFuZAogICAgIyBpZiB0aGUgY29t"\
"cmd1bWVudHMpCiAgICBsb2NhbCBhcmdzPSgiJEAiKQogICAgbG9jYWwgYXJnX2NvdW50PSR7I2Fy"\ "bWFuZCBpcyBiYWNrdXAgb3IgcmVzdG9yZSwgdGhlbiB0aGUgbGFzdCB0d28gYXJndW1lbnRzIGFy"\
"Z3NbQF19CiAgICAKICAgICMgUHJvY2VzcyBhbGwga2V5PXZhbHVlIHBhaXJzCiAgICBmb3IgKChp"\ "ZSB0aGUgYmFja3VwIGZpbGUgYW5kIHRoZSB0ZW1wb3JhcnkgcGF0aAogICAgIyBhbGwgb3RoZXIg"\
"PTA7IGk8JGFyZ19jb3VudDsgaSsrKSk7IGRvCiAgICAgICAgbG9jYWwgcGFpcj0iJHthcmdzWyRp"\ "YXJndW1lbnRzIGFyZSBvZiBmb3JtOgogICAgIyBrZXk9dmFsdWUKICAgICMgd2hlcmUga2V5IGNh"\
"XX0iCiAgICAgICAgCiAgICAgICAgIyBTa2lwIGlmIG5vdCBpbiBrZXk9dmFsdWUgZm9ybWF0CiAg"\ "biBiZSBvbmUgb2Ygdm9sdW1lLCBwYXRoIG9yIGZpbGUuCiAgICAjIHZhbHVlIGlzIHRoZSBwYXRo"\
"ICAgICAgaWYgW1sgIiRwYWlyIiAhPSAqIj0iKiBdXTsgdGhlbgogICAgICAgICAgICBjb250aW51"\ "IG9yIHZvbHVtZSBuYW1lLgoKICAgICMgd2UgaXRlcmF0ZSBvdmVyIHRoZSBrZXk9dmFsdWUgYXJn"\
"ZQogICAgICAgIGZpCiAgICAgICAgCiAgICAgICAgbG9jYWwga2V5PSIke3BhaXIlJT0qfSIKICAg"\ "dW1lbnRzLCBhbmQgZm9yIGVhY2ggd2UgY2FsbDoKICAgICMgICAgYXV0b3J1biA8Y29tbWFuZD4g"\
"ICAgICBsb2NhbCB2YWx1ZT0iJHtwYWlyIyo9fSIKCiAgICAgICAgIyBjcmVhdGUgYmFja3VwIGZv"\ "PGJhY2t1cGZpbGU+IDxrZXk+IDx2YWx1ZT4KCiAgICBsb2NhbCBjb21tYW5kPSIkMSIKICAgIHNo"\
"bGRlciB1bmlxdWUgdG8ga2V5L3ZhbHVlLgogICAgICAgIGxvY2FsIGJmb2xkZXI9JChlY2hvICIk"\ "aWZ0CgogICAgbG9jYWwgYmFja3VwX3RlbXBfcGF0aD0iJDEiCiAgICBzaGlmdAoKICAgIGVjaG8g"\
"e2tleX1fJHt2YWx1ZX0iIHwgdHIgLWNkICdbOmFsbnVtOl1fLScpCiAgICAgICAgbG9jYWwgdGFy"\ "ImF1dG9jb21tYW5kcGFyc2U6IGNvbW1hbmQ9JGNvbW1hbmQgYmFja3VwX3RlbXBfcGF0aD0kYmFj"\
"Z2V0cGF0aD0iJHtiYWNrdXBfdGVtcF9wYXRofS8ke2Jmb2xkZXJ9IgogICAgICAgIG1rZGlyIC1w"\ "a3VwX3RlbXBfcGF0aCIKCiAgICAjIEV4dHJhY3QgdGhlIGJhY2t1cCBmaWxlIGFuZCB0ZW1wIHBh"\
"ICR7dGFyZ2V0cGF0aH0KCiAgICAgICAgIyBLZXkgbXVzdCBiZSBvbmUgb2Ygdm9sdW1lLCBwYXRo"\ "dGggKGxhc3QgdHdvIGFyZ3VtZW50cykKICAgIGxvY2FsIGFyZ3M9KCIkQCIpCiAgICBsb2NhbCBh"\
"IG9yIGZpbGUKICAgICAgICBjYXNlICIka2V5IiBpbgogICAgICAgICAgICB2b2x1bWUpCiAgICAg"\ "cmdfY291bnQ9JHsjYXJnc1tAXX0KICAgIAogICAgIyBQcm9jZXNzIGFsbCBrZXk9dmFsdWUgcGFp"\
"ICAgICAgICAgICBfYXV0b2NvbW1hbmRydW5fdm9sdW1lICIkY29tbWFuZCIgIiR2YWx1ZSIgIiR0"\ "cnMKICAgIGZvciAoKGk9MDsgaTwkYXJnX2NvdW50OyBpKyspKTsgZG8KICAgICAgICBsb2NhbCBw"\
"YXJnZXRwYXRoIgogICAgICAgICAgICAgICAgOzsKICAgICAgICAgICAgcGF0aCkKICAgICAgICAg"\ "YWlyPSIke2FyZ3NbJGldfSIKICAgICAgICAKICAgICAgICAjIFNraXAgaWYgbm90IGluIGtleT12"\
"ICAgICAgIF9hdXRvY29tbWFuZHJ1bl9wYXRoICIkY29tbWFuZCIgIiR2YWx1ZSIgIiR0YXJnZXRw"\ "YWx1ZSBmb3JtYXQKICAgICAgICBpZiBbWyAiJHBhaXIiICE9ICoiPSIqIF1dOyB0aGVuCiAgICAg"\
"YXRoIgogICAgICAgICAgICAgICAgOzsKICAgICAgICAgICAgZmlsZSkKICAgICAgICAgICAgICAg"\ "ICAgICAgIGNvbnRpbnVlCiAgICAgICAgZmkKICAgICAgICAKICAgICAgICBsb2NhbCBrZXk9IiR7"\
"IF9hdXRvY29tbWFuZHJ1bl9maWxlICIkY29tbWFuZCIgIiR2YWx1ZSIgIiR0YXJnZXRwYXRoIgog"\ "cGFpciUlPSp9IgogICAgICAgIGxvY2FsIHZhbHVlPSIke3BhaXIjKj19IgoKICAgICAgICAjIGNy"\
"ICAgICAgICAgICAgICAgOzsKICAgICAgICAgICAgKikKICAgICAgICAgICAgICAgIF9kaWUgIlVu"\ "ZWF0ZSBiYWNrdXAgZm9sZGVyIHVuaXF1ZSB0byBrZXkvdmFsdWUuCiAgICAgICAgbG9jYWwgYmZv"\
"a25vd24ga2V5ICRrZXkgcGFzc2VkIHRvIGF1dG8ke2NvbW1hbmR9LiBXZSBvbmx5IHN1cHBvcnQg"\ "bGRlcj0kKGVjaG8gIiR7a2V5fV8ke3ZhbHVlfSIgfCB0ciAtY2QgJ1s6YWxudW06XV8tJykKICAg"\
"dm9sdW1lLCBwYXRoIGFuZCBmaWxlLiIKICAgICAgICAgICAgICAgIDs7CiAgICAgICAgZXNhYwog"\ "ICAgICBsb2NhbCB0YXJnZXRwYXRoPSIke2JhY2t1cF90ZW1wX3BhdGh9LyR7YmZvbGRlcn0iCiAg"\
"ICAgZG9uZQp9CgoKZGF0YWNyZWF0ZSgpIHsKICAgIF9hdXRvY29tbWFuZHBhcnNlIGNyZWF0ZSBu"\ "ICAgICAgbWtkaXIgLXAgJHt0YXJnZXRwYXRofQoKICAgICAgICAjIEtleSBtdXN0IGJlIG9uZSBv"\
"b25lICIkQCIKfQoKCmRhdGFudWtlKCkgewogICAgX2F1dG9jb21tYW5kcGFyc2UgbnVrZSBub25l"\ "ZiB2b2x1bWUsIHBhdGggb3IgZmlsZQogICAgICAgIGNhc2UgIiRrZXkiIGluCiAgICAgICAgICAg"\
"ICIkQCIKfQoKZGF0YWJhY2t1cCgpIHsKICAgIF9jaGVja19yZXF1aXJlZF9lbnZfdmFycyAiQkFD"\ "IHZvbHVtZSkKICAgICAgICAgICAgICAgIF9hdXRvY29tbWFuZHJ1bl92b2x1bWUgIiRjb21tYW5k"\
"S1VQX0ZJTEUiICJURU1QX0RJUiIKICAgIEJBQ0tVUF9URU1QX1BBVEg9IiRURU1QX0RJUi9iYWNr"\ "IiAiJHZhbHVlIiAiJHRhcmdldHBhdGgiCiAgICAgICAgICAgICAgICA7OwogICAgICAgICAgICBw"\
"dXAiCgoKICAgIG1rZGlyIC1wICIkQkFDS1VQX1RFTVBfUEFUSCIKICAgIGVjaG8gIl9hdXRvY29t"\ "YXRoKQogICAgICAgICAgICAgICAgX2F1dG9jb21tYW5kcnVuX3BhdGggIiRjb21tYW5kIiAiJHZh"\
"bWFuZHBhcnNlIFtiYWNrdXBdIFskQkFDS1VQX1RFTVBfUEFUSF0gWyRAXSIKICAgIF9hdXRvY29t"\ "bHVlIiAiJHRhcmdldHBhdGgiCiAgICAgICAgICAgICAgICA7OwogICAgICAgICAgICBmaWxlKQog"\
"bWFuZHBhcnNlIGJhY2t1cCAiJEJBQ0tVUF9URU1QX1BBVEgiICIkQCIKCiAgICB0YXIgemN2ZiAi"\ "ICAgICAgICAgICAgICAgX2F1dG9jb21tYW5kcnVuX2ZpbGUgIiRjb21tYW5kIiAiJHZhbHVlIiAi"\
"JEJBQ0tVUF9GSUxFIiAtQyAiJEJBQ0tVUF9URU1QX1BBVEgiIC4KfQoKZGF0YXJlc3RvcmUoKSB7"\ "JHRhcmdldHBhdGgiCiAgICAgICAgICAgICAgICA7OwogICAgICAgICAgICAqKQogICAgICAgICAg"\
"CiAgICBfY2hlY2tfcmVxdWlyZWRfZW52X3ZhcnMgIkJBQ0tVUF9GSUxFIiAiVEVNUF9ESVIiCiAg"\ "ICAgICAgX2RpZSAiVW5rbm93biBrZXkgJGtleSBwYXNzZWQgdG8gYXV0byR7Y29tbWFuZH0uIFdl"\
"ICBCQUNLVVBfVEVNUF9QQVRIPSIkVEVNUF9ESVIvcmVzdG9yZSIKCiAgICBlY2hvICJfYXV0b2Nv"\ "IG9ubHkgc3VwcG9ydCB2b2x1bWUsIHBhdGggYW5kIGZpbGUuIgogICAgICAgICAgICAgICAgOzsK"\
"bW1hbmRwYXJzZSBbcmVzdG9yZV0gWyRCQUNLVVBfVEVNUF9QQVRIXSBbJEBdIgoKICAgIG1rZGly"\ "ICAgICAgICBlc2FjCiAgICBkb25lCn0KCgpkYXRhY3JlYXRlKCkgewogICAgX2F1dG9jb21tYW5k"\
"IC1wICIkQkFDS1VQX1RFTVBfUEFUSCIKICAgIHRhciB6eHZmICIkQkFDS1VQX0ZJTEUiIC1DICIk"\ "cGFyc2UgY3JlYXRlIG5vbmUgIiRAIgp9CgoKZGF0YW51a2UoKSB7CiAgICBfYXV0b2NvbW1hbmRw"\
"QkFDS1VQX1RFTVBfUEFUSCIgLS1zdHJpcC1jb21wb25lbnRzPTEKCiAgICBfYXV0b2NvbW1hbmRw"\ "YXJzZSBudWtlIG5vbmUgIiRAIgp9CgpkYXRhYmFja3VwKCkgewogICAgX2NoZWNrX3JlcXVpcmVk"\
"YXJzZSByZXN0b3JlICIkQkFDS1VQX1RFTVBfUEFUSCIgIiRAIgp9Cg=="; "X2Vudl92YXJzICJCQUNLVVBfRklMRSIgIlRFTVBfRElSIgogICAgQkFDS1VQX1RFTVBfUEFUSD0i"\
"JFRFTVBfRElSL2JhY2t1cCIKCgogICAgbWtkaXIgLXAgIiRCQUNLVVBfVEVNUF9QQVRIIgogICAg"\
"ZWNobyAiX2F1dG9jb21tYW5kcGFyc2UgW2JhY2t1cF0gWyRCQUNLVVBfVEVNUF9QQVRIXSBbJEBd"\
"IgogICAgX2F1dG9jb21tYW5kcGFyc2UgYmFja3VwICIkQkFDS1VQX1RFTVBfUEFUSCIgIiRAIgoK"\
"ICAgIHRhciB6Y3ZmICIkQkFDS1VQX0ZJTEUiIC1DICIkQkFDS1VQX1RFTVBfUEFUSCIgLgp9Cgpk"\
"YXRhcmVzdG9yZSgpIHsKICAgIF9jaGVja19yZXF1aXJlZF9lbnZfdmFycyAiQkFDS1VQX0ZJTEUi"\
"ICJURU1QX0RJUiIKICAgIEJBQ0tVUF9URU1QX1BBVEg9IiRURU1QX0RJUi9yZXN0b3JlIgoKICAg"\
"IGVjaG8gIl9hdXRvY29tbWFuZHBhcnNlIFtyZXN0b3JlXSBbJEJBQ0tVUF9URU1QX1BBVEhdIFsk"\
"QF0iCgogICAgbWtkaXIgLXAgIiRCQUNLVVBfVEVNUF9QQVRIIgogICAgdGFyIHp4dmYgIiRCQUNL"\
"VVBfRklMRSIgLUMgIiRCQUNLVVBfVEVNUF9QQVRIIiAtLXN0cmlwLWNvbXBvbmVudHM9MQoKICAg"\
"IF9hdXRvY29tbWFuZHBhcnNlIHJlc3RvcmUgIiRCQUNLVVBfVEVNUF9QQVRIIiAiJEAiCn0K";
// Decode Base64 data // Decode Base64 data
size_t decoded_size = (strlen(filedata_base64) * 3) / 4; size_t decoded_size = (strlen(filedata_base64) * 3) / 4;
@ -234,7 +244,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, 6443138635497166205ULL, std::filesystem::perms(493), decoded_data, actual_size); bool file_written = _recreate_file_(outpath, 12213965674971673660ULL, 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,174 @@
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/output.hpp"
#include "utils/assert.hpp"
#include "utils/utils.hpp"
#include "command_registry.hpp"
#include "config.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "server_env_manager.hpp"
#include "templates.hpp"
#include "utils/directories.hpp"
#include "shared_commands.hpp"
namespace dropshell
{
int backupdata_handler(const CommandContext &ctx);
static std::vector<std::string> backupdata_name_list = {"backupdata", "bd", "backup", "bup"};
// Static registration
struct BackupDataCommandRegister
{
BackupDataCommandRegister()
{
CommandRegistry::instance().register_command({backupdata_name_list,
backupdata_handler,
shared_commands::std_autocomplete_allowall,
false, // hidden
true, // requires_config
true, // requires_install
2, // min_args (after command)
2, // max_args (after command)
"backupdata SERVER SERVICE",
"Backup data for a service on a server.",
// heredoc
R"(
backupdata SERVER SERVICE Backup data for a service on a server.
backupdata SERVER all Backup data for all services on a server.
Note: This command will not create any data or configuration.
It will simply backup the data on the remote server, saving it to a local file.
Restore the data with restore.
)"});
}
} backupdata_command_register;
namespace shared_commands
{
bool backupdata_service(const std::string &server, const std::string &service)
{
server_env_manager server_env(server);
if (!server_env.is_valid())
{
error << "Server " << server << " is not valid" << std::endl;
return false;
}
LocalServiceInfo sinfo = get_service_info(server, service);
if (!SIvalid(sinfo))
{
error << "Service " << service << " is not valid" << std::endl;
return false;
}
const std::string command = "backup";
if (!gTemplateManager().template_command_exists(sinfo.template_name, command))
{
info << service << " has no data to backup" << std::endl;
debug << "(no backup script for " << sinfo.template_name << ")" << std::endl;
return true; // nothing to back up.
}
// Check if basic installed stuff is in place.
std::string remote_service_template_path = remotepath::service_template(server, service);
std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh";
std::string remote_service_config_path = remotepath::service_config(server, service);
if (!server_env.check_remote_items_exist({remotepath::service(server, service),
remote_command_script_file,
remotefile::service_env(server, service)}))
{
error << "Error: Required service directories not found on remote server" << std::endl;
info << "Is the service installed?" << std::endl;
return false;
}
// Create backups directory on server if it doesn't exist
std::string remote_backups_dir = remotepath::backups(server);
debug << "Remote backups directory on " << server << ": " << remote_backups_dir << std::endl;
std::string mkdir_cmd = "mkdir -p " + quote(remote_backups_dir);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Defaults))
{
error << "Failed to create backups directory on server" << std::endl;
return false;
}
// Create backups directory locally if it doesn't exist
std::string local_backups_dir = 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;
}
} // namespace shared_commands
int backupdata_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 2, "Invalid number of arguments");
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
// backup all services on the server
maketitle("Backing up data for all services on " + server);
bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const LocalServiceInfo &si : services)
okay &= shared_commands::backupdata_service(server, si.service_name);
return okay ? 0 : 1;
}
return shared_commands::backupdata_service(server, service) ? 0 : 1;
}
} // namespace dropshell

View File

@ -0,0 +1,44 @@
#include "command_registry.hpp"
#include "config.hpp"
#include "utils/output.hpp"
#include "utils/assert.hpp"
namespace dropshell {
void colours_autocomplete(const CommandContext& ctx) {}
int colours_handler(const CommandContext& ctx)
{
info << "Colours:" << std::endl;
debug << "Debug Example: The quick brown fox jumps over the lazy dog." << std::endl;
info << "Info Example: The quick brown fox jumps over the lazy dog." << std::endl;
warning << "Warning Example: The quick brown fox jumps over the lazy dog." << std::endl;
error << "Error Example: The quick brown fox jumps over the lazy dog." << std::endl;
return 0;
}
static std::vector<std::string> colours_name_list={"colours","c","--colours","-c"};
// Static registration
struct ColoursCommandRegister {
ColoursCommandRegister() {
CommandRegistry::instance().register_command({
colours_name_list,
colours_handler,
colours_autocomplete,
true, // hidden
false, // requires_config
false, // requires_install
0, // min_args (after command)
0, // max_args (after command)
"colours",
"Show the colours used by dropshell.",
// heredoc
R"(
Show the colours used by dropshell.
)"
});
}
} colours_command_register;
} // namespace dropshell

View File

@ -11,7 +11,6 @@ namespace dropshell
{ {
int create_service_handler(const CommandContext &ctx); int create_service_handler(const CommandContext &ctx);
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent);
void create_service_autocomplete(const CommandContext &ctx); void create_service_autocomplete(const CommandContext &ctx);
static std::vector<std::string> create_service_name_list = {"create-service"}; static std::vector<std::string> create_service_name_list = {"create-service"};
@ -45,7 +44,7 @@ namespace dropshell
std::string service = safearg(ctx.args, 1); std::string service = safearg(ctx.args, 1);
std::string template_name = safearg(ctx.args, 2); std::string template_name = safearg(ctx.args, 2);
return create_service(server, template_name, service, false) ? 0 : 1; return shared_commands::create_service(server, template_name, service) ? 0 : 1;
} }
void create_service_autocomplete(const CommandContext &ctx) void create_service_autocomplete(const CommandContext &ctx)
@ -58,12 +57,15 @@ namespace dropshell
{ {
std::set<std::string> templates = gTemplateManager().get_template_list(); std::set<std::string> templates = gTemplateManager().get_template_list();
for (const auto &template_name : templates) for (const auto &template_name : templates)
std::cout << template_name << std::endl; rawout << template_name << std::endl;
} }
} }
} }
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent) namespace shared_commands
{
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name)
{ {
if (server_name.empty() || template_name.empty() || service_name.empty()) if (server_name.empty() || template_name.empty() || service_name.empty())
return false; return false;
@ -72,44 +74,34 @@ namespace dropshell
if (service_dir.empty()) if (service_dir.empty())
{ {
if (!silent) error << "Couldn't locate server " << server_name << " in any config directory" << std::endl;
{ info << "Please check the server name is correct and try again" << std::endl;
std::cerr << "Error: Couldn't locate server " << server_name << " in any config directory" << std::endl; info << "You can list all servers with 'dropshell servers'" << std::endl;
std::cerr << "Please check the server name is correct and try again" << std::endl; info << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
std::cerr << "You can list all servers with 'dropshell servers'" << std::endl;
std::cerr << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl;
}
return false; return false;
} }
if (std::filesystem::exists(service_dir)) if (std::filesystem::exists(service_dir))
{ {
if (!silent) error << "Service already exists: " << service_name << std::endl;
{ debug << "Current service path: " << service_dir << std::endl;
std::cerr << "Error: Service already exists: " << service_name << std::endl;
std::cerr << "Current service path: " << service_dir << std::endl;
}
return false; return false;
} }
template_info tinfo = gTemplateManager().get_template_info(template_name); template_info tinfo = gTemplateManager().get_template_info(template_name);
if (!tinfo.is_set()) if (!tinfo.is_set())
{ {
if (!silent) error << "Template '" << template_name << "' not found" << std::endl;
{ info << "Please check the template name is correct and try again" << std::endl;
std::cerr << "Error: Template '" << template_name << "' not found" << std::endl; info << "You can list all templates with 'dropshell templates'" << std::endl;
std::cerr << "Please check the template name is correct and try again" << std::endl; info << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
std::cerr << "You can list all templates with 'dropshell templates'" << std::endl;
std::cerr << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl;
}
return false; return false;
} }
// check template is all good. // check template is all good.
if (!gTemplateManager().test_template(tinfo.local_template_path())) if (!gTemplateManager().test_template(tinfo.local_template_path()))
{ {
if (!silent) error << "Template '" << template_name << "' is not valid" << std::endl;
std::cerr << "Error: Template '" << template_name << "' is not valid" << std::endl;
return false; return false;
} }
@ -119,15 +111,14 @@ namespace dropshell
// copy the template config files to the service directory // copy the template config files to the service directory
recursive_copy(tinfo.local_template_path() / "config", service_dir); recursive_copy(tinfo.local_template_path() / "config", service_dir);
if (!silent) info << "Service " << service_name << " created successfully" << std::endl;
{ info << std::endl;
std::cout << "Service " << service_name << " created successfully" << std::endl; info << "To complete the installation, please:" << std::endl;
std::cout << std::endl; info << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl;
std::cout << "To complete the installation, please:" << std::endl; info << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
std::cout << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl;
std::cout << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl;
}
return true; return true;
} }
} // namespace shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -66,13 +66,13 @@ 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);
@ -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");
@ -136,56 +134,6 @@ int help_handler(const CommandContext& ctx) {
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

@ -15,6 +15,7 @@
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
#include "utils/assert.hpp" #include "utils/assert.hpp"
#include "servers.hpp"
namespace dropshell namespace dropshell
{ {
@ -43,8 +44,8 @@ namespace dropshell
Install components on a server. This is safe to re-run (non-destructive) and used to update Install components on a server. This is safe to re-run (non-destructive) and used to update
servers and their services. servers and their services.
install (re)install dropshell components on this computer. install (re)install dropshell components on this computer, and on all servers.
install SERVER (re)install dropshell agent on the given server. install SERVER (re)install dropshell agent on the particular given server.
install SERVER [SERVICE|all] (re)install the given service (or all services) on the given server. install SERVER [SERVICE|all] (re)install the given service (or all services) on the given server.
Note you need to create the service first with: Note you need to create the service first with:
@ -53,84 +54,87 @@ namespace dropshell
} }
} install_command_register; } install_command_register;
namespace shared_commands
// ------------------------------------------------------------------------------------------------
// install service over ssh : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool install_service(const std::string &server, const std::string &service, bool silent)
{ {
LocalServiceInfo service_info = get_service_info(server, service);
if (!SIvalid(service_info))
return false;
server_env_manager server_env(server); // ------------------------------------------------------------------------------------------------
if (!server_env.is_valid()) // install service over ssh : SHARED COMMAND
return false; // ------------------------------------------------------------------------------------------------
bool install_service(const std::string &server, const std::string &service)
maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
if (!server_env.is_valid())
return false; // should never hit this.
// Check if template exists
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
if (!tinfo.is_set())
return false;
if (!tinfo.template_valid())
{ {
std::cerr << "Template is not valid: " << service_info.template_name << std::endl; LocalServiceInfo service_info = get_service_info(server, service);
return false; if (!SIvalid(service_info))
} return false;
// Create service directory server_env_manager server_env(server);
std::string remote_service_path = remotepath::service(server, service); if (!server_env.is_valid())
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path); return false;
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
{
std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
return false;
}
// Check if rsync is installed on remote host maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);
std::string check_rsync_cmd = "which rsync";
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent))
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
}
// Copy template files if (!server_env.is_valid())
debug << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl return false; // should never hit this.
// Check if template exists
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
if (!tinfo.is_set())
return false;
if (!tinfo.template_valid())
{
std::cerr << "Template is not valid: " << service_info.template_name << std::endl;
return false;
}
// Create service directory
std::string remote_service_path = remotepath::service(server, service);
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
{
std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
return false;
}
// Check if rsync is installed on remote host
std::string check_rsync_cmd = "which rsync";
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent))
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
}
// Copy template files
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, false))
{ {
std::cerr << "Failed to copy template files using rsync" << std::endl; std::cerr << "Failed to copy template files using rsync" << std::endl;
return false; return false;
} }
// Copy service files // Copy service files
debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl debug << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl
<< std::string(8, ' ') << "[REMOTE] " << remotepath::service_config(server, service) << std::endl; << std::string(8, ' ') << "[REMOTE] " << remotepath::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, false))
{ {
std::cerr << "Failed to copy service files using rsync" << std::endl; std::cerr << "Failed to copy service files using rsync" << std::endl;
return false; return false;
}
// Run install script
{
info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, false, {});
}
// print health tick
info << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true;
} }
// Run install script } // namespace shared_commands
{
info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl;
server_env.run_remote_template_command(service, "install", {}, silent, {});
}
// print health tick
info << "Health: " << shared_commands::healthtick(server, service) << std::endl;
return true;
}
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// update_dropshell // update_dropshell
@ -156,6 +160,8 @@ namespace dropshell
int update_dropshell() int update_dropshell()
{ {
maketitle("Updating dropshell on this computer...");
// determine path to this executable // determine path to this executable
std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe"); std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe");
std::filesystem::path parent_path = dropshell_path.parent_path(); std::filesystem::path parent_path = dropshell_path.parent_path();
@ -199,8 +205,8 @@ namespace dropshell
if (currentver >= newver) if (currentver >= newver)
{ {
std::cout << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl; info << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl;
std::cout << "No update needed." << std::endl; info << "Release version is not newer, no update needed." << std::endl;
return 0; return 0;
} }
@ -211,20 +217,22 @@ namespace dropshell
rval = system(bash_script_2.c_str()); rval = system(bash_script_2.c_str());
if (rval != 0) if (rval != 0)
{ {
std::cerr << "Failed to install new version of dropshell." << std::endl; error << "Failed to install new version of dropshell." << std::endl;
return -1; return -1;
} }
std::cout << "Successfully updated " << dropshell_path << " to the latest " << arch << " version." << std::endl; info << "Successfully updated " << dropshell_path << " to the latest " << arch << " version." << std::endl;
// execute the new version // execute the new version
execlp("bash", "bash", "-c", (parent_path / "dropshell").c_str(), "install", (char *)nullptr); execlp("bash", "bash", "-c", (parent_path / "dropshell").c_str(), "install", (char *)nullptr);
std::cerr << "Failed to execute new version of dropshell." << std::endl; error << "Failed to execute new version of dropshell." << std::endl;
return -1; return -1;
} }
int install_local_agent() int install_local_agent()
{ {
maketitle("Installing dropshell agent on this computer...");
std::vector<std::filesystem::path> paths = { std::vector<std::filesystem::path> paths = {
gConfig().get_local_template_cache_path(), gConfig().get_local_template_cache_path(),
gConfig().get_local_backup_path(), gConfig().get_local_backup_path(),
@ -236,7 +244,7 @@ namespace dropshell
for (auto &p : paths) for (auto &p : paths)
if (!std::filesystem::exists(p)) if (!std::filesystem::exists(p))
{ {
std::cout << "Creating directory: " << p << std::endl; info << "Creating directory: " << p << std::endl;
std::filesystem::create_directories(p); std::filesystem::create_directories(p);
} }
@ -246,39 +254,22 @@ namespace dropshell
std::string cmd = "cd " + localpath::agent() + " && curl -fsSL -o bb64 https://gitea.jde.nz/public/bb64/releases/download/latest/bb64.amd64 && chmod a+x bb64"; std::string cmd = "cd " + localpath::agent() + " && curl -fsSL -o bb64 https://gitea.jde.nz/public/bb64/releases/download/latest/bb64.amd64 && chmod a+x bb64";
int ret = system(cmd.c_str()); int ret = system(cmd.c_str());
if (EXITSTATUSCHECK(ret)) if (EXITSTATUSCHECK(ret))
std::cout << "Downloaded local bb64 to " << localpath::agent() << std::endl; info << "Downloaded local bb64 to " << localpath::agent() << std::endl;
else else
std::cerr << "Failed to download local bb64 to " << localpath::agent() << std::endl; error << "Failed to download local bb64 to " << localpath::agent() << std::endl;
} }
else else
{ {
std::cout << "Updating local bb64..." << std::endl; info << "Updating local bb64..." << std::endl;
system((localpath::agent() + "bb64 -u").c_str()); // update. system((localpath::agent() + "bb64 -u").c_str()); // update.
} }
std::cout << "Creating local files to copy to remote agents..." << std::endl; info << "Creating local files to copy to remote agents..." << std::endl;
recreate_agent::recreate_tree(localpath::files_for_remote_agent()); recreate_agent::recreate_tree(localpath::files_for_remote_agent());
return 0; return 0;
} }
int install_host()
{
// update dropshell.
// install the local dropshell agent.
int rval = update_dropshell();
if (rval != 0)
return rval;
rval = install_local_agent();
if (rval != 0)
return rval;
std::cout << "Installation complete." << std::endl;
return 0;
}
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.
@ -305,14 +296,15 @@ namespace dropshell
info << "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!
info << "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 +
" 'mkdir -p " + quote(agent_path) + " && curl -fsSL \"https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh\" | bash -s -- " + " 'mkdir -p " + quote(agent_path) + " && curl -fsSL \"https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh\" | bash -s -- " +
quote(agent_path) + " " + quote("$(id -u " + server_env.get_SSH_USER() + "):$(id -g " + server_env.get_SSH_USER() + ")") + "'"; 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))
error << "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
@ -330,6 +322,35 @@ namespace dropshell
return 0; return 0;
} }
// ------------------------------------------------------------------------------------------------
// install_host
// ------------------------------------------------------------------------------------------------
int install_host()
{
// update dropshell.
// install the local dropshell agent.
int rval = update_dropshell();
if (rval != 0)
return rval;
rval = install_local_agent();
if (rval != 0)
return rval;
// install the dropshell agent on all servers.
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto &server : servers)
{
rval = install_server(server.name);
if (rval != 0)
return rval;
}
std::cout << "Installation complete." << std::endl;
return 0;
}
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// install command implementation // install command implementation
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -362,7 +383,7 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server); std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services) for (const auto &service : services)
{ {
if (!install_service(server, service.service_name, false)) if (!shared_commands::install_service(server, service.service_name))
okay = false; okay = false;
} }
return okay ? 0 : 1; return okay ? 0 : 1;
@ -370,7 +391,7 @@ namespace dropshell
else else
{ // install the specific service. { // install the specific service.
std::string service = safearg(ctx.args, 1); std::string service = safearg(ctx.args, 1);
return install_service(server, service, false) ? 0 : 1; return shared_commands::install_service(server, service) ? 0 : 1;
} }
} }

View File

@ -130,7 +130,7 @@ void show_server_details(const std::string& server_name) {
info << std::string(40, '-') << std::endl; info << std::string(40, '-') << std::endl;
// Try to connect to the server // Try to connect to the server
std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " '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) {
info << "Status: Online" << std::endl; info << "Status: Online" << std::endl;

View File

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

View File

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

View File

@ -5,6 +5,7 @@
#include "directories.hpp" #include "directories.hpp"
#include "services.hpp" #include "services.hpp"
#include "servers.hpp" #include "servers.hpp"
#include "utils/output.hpp"
namespace dropshell namespace dropshell
{ {
@ -12,239 +13,318 @@ namespace dropshell
namespace shared_commands namespace shared_commands
{ {
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// std_autocomplete : SHARED COMMAND // std_autocomplete : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
void std_autocomplete(const CommandContext &ctx) void std_autocomplete(const CommandContext &ctx)
{
if (ctx.args.size() == 0)
{ // just the command, no args yet.
// list servers
std::vector<ServerInfo> servers = get_configured_servers();
for (const auto &server : servers)
{
std::cout << server.name << std::endl;
}
}
else if (ctx.args.size() == 1)
{ {
// list services if (ctx.args.size() == 0)
std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]); { // just the command, no args yet.
for (const auto &service : services) // list servers
{ std::vector<ServerInfo> servers = get_configured_servers();
std::cout << service.service_name << std::endl; for (const auto &server : servers)
} {
} rawout << server.name << std::endl;
}
// ------------------------------------------------------------------------------------------------
// std_autocomplete_allowall : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
void std_autocomplete_allowall(const CommandContext &ctx)
{
std_autocomplete(ctx);
if (ctx.args.size() == 1)
std::cout << "all" << std::endl;
}
// ------------------------------------------------------------------------------------------------
// rsync_tree_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool rsync_tree_to_remote(
const std::string &local_path,
const std::string &remote_path,
server_env_manager &server_env,
bool silent)
{
ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync.");
std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " +
quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" +
remote_path + "/");
return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
// ------------------------------------------------------------------------------------------------
// get_arch : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string get_arch()
{
// determine the architecture of the system
std::string arch;
#ifdef __aarch64__
arch = "arm64";
#elif __x86_64__
arch = "amd64";
#endif
return arch;
}
// ------------------------------------------------------------------------------------------------
// cRemoteTempFolder : SHARED CLASS
// ------------------------------------------------------------------------------------------------
cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env)
{
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10);
std::string mkdir_cmd = "mkdir -p " + quote(p);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
std::cerr << "Failed to create temp directory on server" << std::endl;
else
mPath = p;
}
cRemoteTempFolder::~cRemoteTempFolder()
{
std::string rm_cmd = "rm -rf " + quote(mPath);
execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent);
}
std::string cRemoteTempFolder::path() const
{
return mPath;
}
// ------------------------------------------------------------------------------------------------
// get_all_services_status : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name)
{
std::map<std::string, ServiceStatus> status;
server_env_manager env(server_name);
if (!env.is_valid())
{
std::cerr << "Error: Invalid server environment" << std::endl;
return status;
}
std::string output;
if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name),
"./_allservicesstatus.sh",
{{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}),
cMode::CaptureOutput | cMode::Silent,
&output))
return status;
std::stringstream ss(output);
std::string line;
while (std::getline(ss, line))
{
std::string key, value;
std::size_t pos = line.find("=");
if (pos != std::string::npos)
{
key = dequote(trim(line.substr(0, pos)));
value = dequote(trim(line.substr(pos + 1)));
// decode key, it's of format SERVICENAME_[HEALTH|PORTS]
std::string service_name = key.substr(0, key.find_last_of("_"));
std::string status_type = key.substr(key.find_last_of("_") + 1);
if (status_type == "HEALTH")
{ // healthy|unhealthy|unknown
if (value == "healthy")
status[service_name].health = HealthStatus::HEALTHY;
else if (value == "unhealthy")
status[service_name].health = HealthStatus::UNHEALTHY;
else if (value == "unknown")
status[service_name].health = HealthStatus::UNKNOWN;
else
status[service_name].health = HealthStatus::ERROR;
} }
else if (status_type == "PORTS") }
{ // port1,port2,port3 else if (ctx.args.size() == 1)
std::vector<std::string> ports = string2multi(value); {
for (const auto &port : ports) // list services
{ std::vector<LocalServiceInfo> services = get_server_services_info(ctx.args[0]);
if (port != "unknown") for (const auto &service : services)
status[service_name].ports.push_back(str2int(port)); {
rawout << service.service_name << std::endl;
}
}
}
// ------------------------------------------------------------------------------------------------
// std_autocomplete_allowall : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
void std_autocomplete_allowall(const CommandContext &ctx)
{
std_autocomplete(ctx);
if (ctx.args.size() == 1)
rawout << "all" << std::endl;
}
// ------------------------------------------------------------------------------------------------
// rsync_tree_to_remote : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
bool rsync_tree_to_remote(
const std::string &local_path,
const std::string &remote_path,
server_env_manager &server_env,
bool silent)
{
ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync.");
std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " +
quote(local_path + "/") + " " +
quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" +
remote_path + "/");
return execute_local_command("", rsync_cmd, {}, nullptr, (silent ? cMode::Silent : cMode::Defaults));
}
// ------------------------------------------------------------------------------------------------
// get_arch : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string get_arch()
{
// determine the architecture of the system
std::string arch;
#ifdef __aarch64__
arch = "arm64";
#elif __x86_64__
arch = "amd64";
#endif
return arch;
}
// ------------------------------------------------------------------------------------------------
// cRemoteTempFolder : SHARED CLASS
// ------------------------------------------------------------------------------------------------
cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env)
{
std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10);
std::string mkdir_cmd = "mkdir -p " + quote(p);
if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
error << "Failed to create temp directory on server" << std::endl;
else
mPath = p;
}
cRemoteTempFolder::~cRemoteTempFolder()
{
std::string rm_cmd = "rm -rf " + quote(mPath);
execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent);
}
std::string cRemoteTempFolder::path() const
{
return mPath;
}
// ------------------------------------------------------------------------------------------------
// get_all_services_status : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::map<std::string, ServiceStatus> get_all_services_status(std::string server_name)
{
std::map<std::string, ServiceStatus> status;
server_env_manager env(server_name);
if (!env.is_valid())
{
error << "Invalid server environment" << std::endl;
return status;
}
std::string output;
if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}),
cMode::CaptureOutput | cMode::Silent,
&output))
return status;
std::stringstream ss(output);
std::string line;
while (std::getline(ss, line))
{
std::string key, value;
std::size_t pos = line.find("=");
if (pos != std::string::npos)
{
key = dequote(trim(line.substr(0, pos)));
value = dequote(trim(line.substr(pos + 1)));
// decode key, it's of format SERVICENAME_[HEALTH|PORTS]
std::string service_name = key.substr(0, key.find_last_of("_"));
std::string status_type = key.substr(key.find_last_of("_") + 1);
if (status_type == "HEALTH")
{ // healthy|unhealthy|unknown
if (value == "healthy")
status[service_name].health = HealthStatus::HEALTHY;
else if (value == "unhealthy")
status[service_name].health = HealthStatus::UNHEALTHY;
else if (value == "unknown")
status[service_name].health = HealthStatus::UNKNOWN;
else
status[service_name].health = HealthStatus::ERROR;
}
else if (status_type == "PORTS")
{ // port1,port2,port3
std::vector<std::string> ports = string2multi(value);
for (const auto &port : ports)
{
if (port != "unknown")
status[service_name].ports.push_back(str2int(port));
}
} }
} }
} }
return status;
} }
return status;
}
// ------------------------------------------------------------------------------------------------
// healthtick : SHARED COMMAND
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// healthtick : SHARED COMMAND std::string healthtick(const std::string &server, const std::string &service)
// ------------------------------------------------------------------------------------------------
std::string healthtick(const std::string &server, const std::string &service)
{
std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m";
std::string yellow_exclamation = "\033[33m!\033[0m";
std::string unknown = "\033[37m✓\033[0m";
HealthStatus status = is_healthy(server, service);
if (status == HealthStatus::HEALTHY)
return green_tick;
else if (status == HealthStatus::UNHEALTHY)
return red_cross;
else if (status == HealthStatus::UNKNOWN)
return unknown;
else
return yellow_exclamation;
}
// ------------------------------------------------------------------------------------------------
// HealthStatus2String : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string HealthStatus2String(HealthStatus status)
{
if (status == HealthStatus::HEALTHY)
return ":tick:";
else if (status == HealthStatus::UNHEALTHY)
return ":cross:";
else if (status == HealthStatus::UNKNOWN)
return ":greytick:";
else if (status == HealthStatus::NOTINSTALLED)
return ":warning:";
else
return ":error:";
}
// ------------------------------------------------------------------------------------------------
// is_healthy : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
HealthStatus is_healthy(const std::string &server, const std::string &service)
{
server_env_manager env(server);
if (!env.is_valid())
{ {
std::cerr << "Error: Server service not initialized" << std::endl; std::string green_tick = "\033[32m✓\033[0m";
return HealthStatus::ERROR; std::string red_cross = "\033[31m✗\033[0m";
std::string yellow_exclamation = "\033[33m!\033[0m";
std::string unknown = "\033[37m✓\033[0m";
HealthStatus status = is_healthy(server, service);
if (status == HealthStatus::HEALTHY)
return green_tick;
else if (status == HealthStatus::UNHEALTHY)
return red_cross;
else if (status == HealthStatus::UNKNOWN)
return unknown;
else
return yellow_exclamation;
} }
if (!env.check_remote_dir_exists(remotepath::service(server, service))) // ------------------------------------------------------------------------------------------------
// HealthStatus2String : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
std::string HealthStatus2String(HealthStatus status)
{ {
return HealthStatus::NOTINSTALLED; if (status == HealthStatus::HEALTHY)
return ":tick:";
else if (status == HealthStatus::UNHEALTHY)
return ":cross:";
else if (status == HealthStatus::UNKNOWN)
return ":greytick:";
else if (status == HealthStatus::NOTINSTALLED)
return ":warning:";
else
return ":error:";
} }
std::string script_path = remotepath::service_template(server, service) + "/status.sh"; // ------------------------------------------------------------------------------------------------
if (!env.check_remote_file_exists(script_path)) // is_healthy : SHARED COMMAND
// ------------------------------------------------------------------------------------------------
HealthStatus is_healthy(const std::string &server, const std::string &service)
{ {
return HealthStatus::UNKNOWN; server_env_manager env(server);
if (!env.is_valid())
{
error << "Server service not initialized" << std::endl;
return HealthStatus::ERROR;
}
if (!env.check_remote_dir_exists(remotepath::service(server, service)))
{
return HealthStatus::NOTINSTALLED;
}
std::string script_path = remotepath::service_template(server, service) + "/status.sh";
if (!env.check_remote_file_exists(script_path))
{
return HealthStatus::UNKNOWN;
}
// Run status script, does not display output.
if (!env.run_remote_template_command(service, "status", {}, true, {}))
return HealthStatus::UNHEALTHY;
return HealthStatus::HEALTHY;
} }
// Run status script, does not display output. // ------------------------------------------------------------------------------------------------
if (!env.run_remote_template_command(service, "status", {}, true, {})) // healthmark : SHARED COMMAND
return HealthStatus::UNHEALTHY; // ------------------------------------------------------------------------------------------------
return HealthStatus::HEALTHY; std::string healthmark(const std::string &server, const std::string &service)
} {
HealthStatus status = is_healthy(server, service);
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)
// healthmark : SHARED COMMAND {
// ------------------------------------------------------------------------------------------------ // Parse the filename according to the format:
std::string healthmark(const std::string &server, const std::string &service) // server + magic_string() + template_name + magic_string() + service + magic_string() + datetime + ".tgz"
{ std::string name = filename;
HealthStatus status = is_healthy(server, service); if (name.size() > 4 && name.substr(name.size() - 4) == ".tgz")
return HealthStatus2String(status); 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

View File

@ -57,6 +57,44 @@ 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);
// defined in backupdata.cpp, used by restoredata.cpp.
bool backupdata_service(const std::string& server, const std::string& service);
// defined in uninstall.cpp
bool uninstall_service(const std::string &server, const std::string &service);
// defined in nuke.cpp
bool nuke_service(const std::string &server, const std::string &service);
// defined in install.cpp
bool install_service(const std::string &server, const std::string &service);
// defined in create-service.cpp
bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name);
} // namespace shared_commands } // namespace shared_commands
} // namespace dropshell } // namespace dropshell

View File

@ -45,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);
@ -57,26 +57,26 @@ 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;
} }
LocalServiceInfo sinfo = get_service_info(server, service); LocalServiceInfo sinfo = get_service_info(server, service);
if (!SIvalid(sinfo)) if (!SIvalid(sinfo))
{ {
std::cerr << "Error: Service " << service << " is not valid" << std::endl; error << "Service " << service << " is not valid" << std::endl;
return false; return false;
} }
if (!gTemplateManager().has_template(sinfo.template_name)) if (!gTemplateManager().has_template(sinfo.template_name))
{ {
std::cerr << "Error: Template " << sinfo.template_name << " is not valid" << std::endl; error << "Template " << sinfo.template_name << " is not valid" << std::endl;
return false; return false;
} }
if (!gTemplateManager().template_command_exists(sinfo.template_name, "ssh")) if (!gTemplateManager().template_command_exists(sinfo.template_name, "ssh"))
{ {
std::cerr << "Error: Template " << sinfo.template_name << " does not have an ssh command" << std::endl; error << "Template " << sinfo.template_name << " does not have an ssh command" << std::endl;
return false; return false;
} }
@ -88,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

@ -41,51 +41,49 @@ namespace dropshell
} }
} uninstall_command_register; } uninstall_command_register;
bool uninstall_service(const std::string &server, const std::string &service, bool silent = false) namespace shared_commands {
{ bool uninstall_service(const std::string &server, const std::string &service)
if (!silent) {
maketitle("Uninstalling " + service + " on " + server); maketitle("Uninstalling " + service + " on " + server);
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
if (!server_env.check_remote_dir_exists(remotepath::service(server, service)))
{
error << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall
}
// 3. Run uninstall script if it exists
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
warning << "Uninstall script failed, but continuing with directory removal" << std::endl;
// 4. Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), false))
{
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
info << "Removed remote service directory " << remotepath::service(server, service) << std::endl;
}
else
warning << "Failed to remove remote service directory" << std::endl;
info << "Completed service " << service << " uninstall on " << server << std::endl;
return true;
} }
} // namespace shared_commands
// 2. Check if service directory exists on server
if (!server_env.check_remote_dir_exists(remotepath::service(server, service)))
{
std::cerr << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall
}
// 3. Run uninstall script if it exists
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, silent, {}))
if (!silent)
std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl;
// 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))
{
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
if (!silent)
std::cout << "Removed remote service directory " << remotepath::service(server, service) << std::endl;
}
else if (!silent)
std::cerr << "Warning: Failed to remove remote service directory" << std::endl;
if (!silent)
std::cout << "Completed service " << service << " uninstall on " << server << std::endl;
return true;
}
int uninstall_handler(const CommandContext &ctx) int uninstall_handler(const CommandContext &ctx)
{ {
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;
} }
@ -98,14 +96,14 @@ namespace dropshell
std::vector<LocalServiceInfo> services = get_server_services_info(server); std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services) for (const auto &service : services)
{ {
if (!uninstall_service(server, service.service_name)) if (!shared_commands::uninstall_service(server, service.service_name))
okay = false; okay = false;
} }
return okay ? 0 : 1; return okay ? 0 : 1;
} }
std::string service = safearg(ctx.args, 1); std::string service = safearg(ctx.args, 1);
return uninstall_service(server, service) ? 0 : 1; return shared_commands::uninstall_service(server, service) ? 0 : 1;
} }
} // namespace dropshell } // namespace dropshell

View File

@ -6,6 +6,9 @@
#include "utils/json.hpp" #include "utils/json.hpp"
#include <filesystem> #include <filesystem>
#include "utils/execute.hpp" #include "utils/execute.hpp"
#include "output.hpp"
namespace dropshell { namespace dropshell {
@ -150,7 +153,7 @@ std::vector<std::string> config::get_local_server_definition_paths() {
if (path.is_string() && !path.empty()) if (path.is_string() && !path.empty())
paths.push_back(path); paths.push_back(path);
else else
std::cerr << "Warning: Invalid server definition path: " << path << std::endl; warning << "Invalid server definition path: " << path << std::endl;
} }
return paths; return paths;
} }

View File

@ -50,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) {
@ -116,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)
@ -122,12 +121,12 @@ bool service_runner::nuke(bool silent)
std::string remote_service_path = remotepath::service(mServer, mService); std::string remote_service_path = remotepath::service(mServer, mService);
std::cout << "Service " << mService << " successfully nuked from " << mServer << std::endl; info << "Service " << mService << " successfully nuked from " << mServer << std::endl;
if (!silent) { if (!silent) {
std::cout << "There's nothing left on the remote server." << std::endl; info << "There's nothing left on the remote server." << std::endl;
std::cout << "You can remove the local files with:" << std::endl; info << "You can remove the local files with:" << std::endl;
std::cout << " rm -rf " << localpath::service(mServer,mService) << std::endl; info << " rm -rf " << localpath::service(mServer,mService) << std::endl;
} }
return true; return true;
} }
@ -136,19 +135,19 @@ bool service_runner::fullnuke()
{ {
if (!nuke(true)) if (!nuke(true))
{ {
std::cerr << "Warning: Nuke script failed, aborting fullnuke!" << std::endl; warning << "Nuke script failed, aborting." << std::endl;
return false; return false;
} }
std::string local_service_path = mServiceInfo.local_service_path; std::string local_service_path = mServiceInfo.local_service_path;
if (local_service_path.empty() || !fs::exists(local_service_path)) { if (local_service_path.empty() || !fs::exists(local_service_path)) {
std::cerr << "Error: Service directory not found: " << local_service_path << std::endl; error << "Service directory not found: " << local_service_path << std::endl;
return false; return false;
} }
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; error << "Failed to remove service directory" << std::endl;
return false; return false;
} }
@ -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

@ -47,7 +47,7 @@ std::vector<LocalServiceInfo> get_server_services_info(const std::string& server
if (!service.local_service_path.empty()) if (!service.local_service_path.empty())
services.push_back(service); services.push_back(service);
else else
std::cerr << "Warning: Failed to get service info for " << dirname << " on server " << server_name << std::endl; warning << "Failed to get service info for " << dirname << " on server " << server_name << std::endl;
} }
} // end of for } // end of for
} }
@ -188,7 +188,7 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
all_env_vars.merge(env_vars); all_env_vars.merge(env_vars);
} }
else else
std::cout << "Warning: Expected environment file not found: " << file << std::endl; warning << "Expected environment file not found: " << file << std::endl;
}; };
// Load environment files // Load environment files
@ -198,11 +198,10 @@ bool get_all_service_env_vars(const std::string &server_name, const std::string
// determine template name. // determine template name.
auto it = all_env_vars.find("TEMPLATE"); auto it = all_env_vars.find("TEMPLATE");
if (it == all_env_vars.end()) { if (it == all_env_vars.end()) {
std::cerr << std::endl << std::endl; error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
std::cerr << "Error: TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl; info << "The TEMPLATE variable is required to determine the template name." << std::endl;
std::cerr << "The TEMPLATE variable is required to determine the template name." << std::endl; info << "Please check the service.env file and the .template_info.env file in:" << std::endl;
std::cerr << "Please check the service.env file and the .template_info.env file in:" << std::endl; info << " " << localpath::service(server_name, service_name) << std::endl << std::endl;
std::cerr << " " << localpath::service(server_name, service_name) << std::endl << std::endl;
return false; return false;
} }
template_info tinfo = gTemplateManager().get_template_info(it->second); template_info tinfo = gTemplateManager().get_template_info(it->second);

View File

@ -1,10 +1,13 @@
#include "directories.hpp" #include "directories.hpp"
#include "config.hpp" #include "config.hpp"
#include "server_env_manager.hpp" #include "server_env_manager.hpp"
#include "output.hpp"
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace dropshell { namespace dropshell {
@ -78,7 +81,7 @@ namespace localpath {
std::filesystem::path homedir_path(homedir); std::filesystem::path homedir_path(homedir);
return fs::canonical(homedir_path).string(); return fs::canonical(homedir_path).string();
} }
std::cerr << "Warning: Couldn't determine user directory" << std::endl; warning << "Couldn't determine user directory" << std::endl;
return std::string(); return std::string();
} }
} // namespace localpath } // namespace localpath

View File

@ -11,7 +11,8 @@ namespace dropshell
constexpr const char *GREY = "\033[90m"; constexpr const char *GREY = "\033[90m";
constexpr const char *RESET = "\033[0m"; constexpr const char *RESET = "\033[0m";
constexpr const char *DEBUG_COLOUR = "\033[36m"; // Cyan constexpr const char *DEBUG_COLOUR = "\033[36m"; // Cyan
constexpr const char *INFO_COLOUR = "\033[32m"; // Green // 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 *WARNING_COLOUR = "\033[33m"; // Yellow
constexpr const char *ERROR_COLOUR = "\033[31m"; // Red constexpr const char *ERROR_COLOUR = "\033[31m"; // Red
@ -77,6 +78,9 @@ namespace dropshell
std::ostream &warning = warning_stream; std::ostream &warning = warning_stream;
std::ostream &error = error_stream; std::ostream &error = error_stream;
std::ostream &rawout = std::cout;
std::ostream &rawerr = std::cerr;
std::ostream &colourstream(sColour colour) std::ostream &colourstream(sColour colour)
{ {
switch (colour) switch (colour)

View File

@ -56,6 +56,9 @@ extern std::ostream& info;
extern std::ostream& warning; extern std::ostream& warning;
extern std::ostream& error; extern std::ostream& error;
extern std::ostream& rawout;
extern std::ostream& rawerr;
// Enum for colours // Enum for colours
enum class sColour { enum class sColour {
RESET, RESET,

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,6 +10,16 @@
namespace dropshell { namespace dropshell {
std::string magic_string() {
return "-_-";
}
bool has_magic_string(std::string name)
{
return name.find(magic_string()) != std::string::npos;
}
void maketitle(const std::string& title, sColour colour) { void maketitle(const std::string& title, sColour colour) {
colourstream(colour) << std::string(title.length() + 4, '-') << std::endl; colourstream(colour) << std::string(title.length() + 4, '-') << std::endl;
colourstream(colour) << "| " << title << " |" << std::endl; colourstream(colour) << "| " << title << " |" << std::endl;
@ -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

@ -16,6 +16,8 @@ void maketitle(const std::string& title, sColour colour=sColour::INFO);
bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line); bool replace_line_in_file(const std::string& file_path, const std::string& search_string, const std::string& replacement_line);
std::string magic_string();
bool has_magic_string(std::string name);
// utility functions // utility functions
std::string trim(std::string str); std::string trim(std::string str);
@ -47,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);