Compare commits

..

5 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
13 changed files with 768 additions and 593 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

@ -16,157 +16,159 @@
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
namespace dropshell
{
namespace dropshell { int backupdata_handler(const CommandContext &ctx);
int backupdata_handler(const CommandContext& ctx); static std::vector<std::string> backupdata_name_list = {"backupdata", "bd", "backup", "bup"};
static std::vector<std::string> backupdata_name_list={"backupdata","bd","backup","bup"}; // Static registration
struct BackupDataCommandRegister
// Static registration {
struct BackupDataCommandRegister { BackupDataCommandRegister()
BackupDataCommandRegister() { {
CommandRegistry::instance().register_command({ CommandRegistry::instance().register_command({backupdata_name_list,
backupdata_name_list, backupdata_handler,
backupdata_handler, shared_commands::std_autocomplete_allowall,
shared_commands::std_autocomplete_allowall, false, // hidden
false, // hidden true, // requires_config
true, // requires_config true, // requires_install
true, // requires_install 2, // min_args (after command)
2, // min_args (after command) 2, // max_args (after command)
2, // max_args (after command) "backupdata SERVER SERVICE",
"backupdata SERVER SERVICE", "Backup data for a service on a server.",
"Backup data for a service on a server.", // heredoc
// heredoc R"(
R"(
backupdata SERVER SERVICE Backup data for a service on a server. backupdata SERVER SERVICE Backup data for a service on a server.
backupdata SERVER all Backup data for all services on a server. backupdata SERVER all Backup data for all services on a server.
Note: This command will not create any data or configuration. 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. It will simply backup the data on the remote server, saving it to a local file.
Restore the data with restore. Restore the data with restore.
)" )"});
}); }
} } backupdata_command_register;
} 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); bool backupdata_service(const std::string &server, const std::string &service)
if (!SIvalid(sinfo)) {
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)
{ {
error << "Service " << service << " is not valid" << std::endl; ASSERT(ctx.args.size() == 2, "Invalid number of arguments");
return false;
}
const std::string command = "backup"; std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (!gTemplateManager().template_command_exists(sinfo.template_name, command)) { if (service == "all")
info << service << " has no data to backup" << std::endl; {
debug << "(no backup script for " << sinfo.template_name << ")" << std::endl; // backup all services on the server
return true; // nothing to back up. maketitle("Backing up data for all services on " + server);
} bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server);
// Check if basic installed stuff is in place. for (const LocalServiceInfo &si : services)
std::string remote_service_template_path = remotepath::service_template(server, service); okay &= shared_commands::backupdata_service(server, si.service_name);
std::string remote_command_script_file = remote_service_template_path + "/" + command + ".sh"; return okay ? 0 : 1;
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 return shared_commands::backupdata_service(server, service) ? 0 : 1;
if (!shared_commands::scp_file_from_remote(server_env, remote_backup_file_path, local_backup_file_path, false)) {
error << "Failed to copy backup file from server" << std::endl;
return false;
}
} // dtor of remote_temp_folder will clean up the temp folder on the server
info << "Backup created successfully. Restore with:"<<std::endl;
info << " dropshell restore " << server << " " << service << " " << backup_filename << std::endl;
return true;
}
int backupdata_handler(const CommandContext& ctx)
{
if (ctx.args.size() < 1)
{
error << "Server name is required" << std::endl;
return 1;
} }
std::string server = safearg(ctx.args, 0);
if (ctx.args.size() < 2)
{
// backup all services on the server
maketitle("Backing up data for all services on " + server);
bool okay = true;
std::vector<LocalServiceInfo> services = get_server_services_info(server);
for (const auto &service : services)
okay &= backupdata_service(server, service.service_name);
return okay ? 0 : 1;
}
std::string service = safearg(ctx.args, 1);
return backupdata_service(server, service);
}
} // namespace dropshell } // 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

@ -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
@ -327,7 +319,36 @@ namespace dropshell
} }
info << "Installation on " << server << " complete." << std::endl; info << "Installation on " << server << " complete." << std::endl;
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;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -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

@ -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))
error << "Warning: Invalid service: " << service << std::endl;
if (server_env.check_remote_dir_exists(remotepath::service(server, service)))
{ {
// run the nuke script on the remote server if it exists. 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())
{ {
info << "Running nuke script for " << service << " on " << server << std::endl; LocalServiceInfo service_info;
if (!server_env.run_remote_template_command(service, "nuke", {}, false, {}))
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
{ {
info << "No nuke script found for " << service << " on " << server << std::endl; auto itemsdeleted = std::filesystem::remove_all(local_service_path);
info << "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;
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;
info << "Remote service directory removed: " << remotepath::service(server, service) << std::endl; return 1;
} }
else
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
warning << "Service not found on remote server: " << remotepath::service(server, service) << std::endl;
}
else
warning << "Can't nuke the remote service as the server is invalid: " << server << std::endl;
// step 2 - nuke the local service directory.
std::string local_service_path = localpath::service(server, service);
if (local_service_path.empty() || !std::filesystem::exists(local_service_path))
{
warning << "Local service directory not found: " << local_service_path << std::endl;
}
else
{
auto itemsdeleted = std::filesystem::remove_all(local_service_path);
if (itemsdeleted == 0)
error << "Failed to remove local service directory" << std::endl;
}
info << "Nuked service " << service << " on server " << server << std::endl;
return 0;
}
int nuke_handler(const CommandContext &ctx)
{
ASSERT(ctx.args.size() == 2, "Usage: nuke SERVER SERVICE|all (requires 2 args - you supplied " + std::to_string(ctx.args.size()) + ")");
ASSERT(gConfig().is_config_set(), "No configuration found. Please run 'dropshell config' to set up your configuration.");
std::string server = safearg(ctx.args, 0);
std::string service = safearg(ctx.args, 1);
if (service == "all")
{
int rval = 0;
// iterate through all service folders in the server directory.
std::string server_path = localpath::server(server);
if (server_path.empty())
{ {
error << "Server not found: " << server << std::endl; return (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

@ -16,122 +16,243 @@
#include "utils/directories.hpp" #include "utils/directories.hpp"
#include "shared_commands.hpp" #include "shared_commands.hpp"
namespace dropshell
{
namespace dropshell { int restoredata_handler(const CommandContext &ctx);
int restoredata_handler(const CommandContext& ctx); void restoredata_autocomplete(const CommandContext &ctx);
void restoredata_autocomplete(const CommandContext& ctx); static std::vector<std::string> restoredata_name_list = {"restoredata", "rd", "restore", "rest"};
// Static registration
static std::vector<std::string> restoredata_name_list={"restoredata","rd","restore","rest"}; struct RestoreDataCommandRegister
{
// Static registration RestoreDataCommandRegister()
struct RestoreDataCommandRegister { {
RestoreDataCommandRegister() { CommandRegistry::instance().register_command({restoredata_name_list,
CommandRegistry::instance().register_command({ restoredata_handler,
restoredata_name_list, restoredata_autocomplete,
restoredata_handler, false, // hidden
restoredata_autocomplete, true, // requires_config
false, // hidden true, // requires_install
true, // requires_config 3, // min_args (after command)
true, // requires_install 3, // max_args (after command)
3, // min_args (after command) "restoredata SERVER SERVICE BACKUP_FILE|latest",
3, // max_args (after command) "Restore data for a service on a server, overwriting the existing data.",
"restoredata SERVER SERVICE BACKUP_FILE", // heredoc
"Restore data for a service on a server, overwriting the existing data.", R"(
// heredoc
R"(
restoredata SERVER SERVICE BACKUP_FILE Restore data to a service on a server. Destructive. 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 Note: This command will not create any service configuration, you need
to have a valid service installed first. to have a valid service installed first.
The backup file must be in the local backups directory. The backup file must be in the local backups directory.
WARNING: This will permanently overwrite the service's data on the remote server! WARNING: This will permanently overwrite the service's data on the remote server!
)" )"});
}); }
} } restoredata_command_register;
} 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 = "")
int restoredata_handler(const CommandContext& ctx)
{
ASSERT(ctx.args.size() == 2, "Invalid number of arguments");
std::string server = ctx.args[0];
std::string service = ctx.args[1];
std::string backup_file = ctx.args[2];
server_env_manager server_env(server);
if (!server_env.is_valid()) {
error << "Server " << server << " is not valid" << std::endl;
return 1;
}
shared_commands::cBackupFileName backup_details(backup_file);
if (!backup_details.is_valid()) {
error << "Invalid backup file: " << backup_file << std::endl;
return 1;
}
return 0;
}
void restoredata_autocomplete(const CommandContext& ctx)
{
shared_commands::std_autocomplete(ctx);
if (ctx.args.size() == 2) // next arg is the backup file
{ {
std::string server = ctx.args[0]; std::string local_backups_dir = gConfig().get_local_backup_path();
std::string service = ctx.args[1]; 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; std::vector<shared_commands::cBackupFileName> backups;
for (const auto &entry : std::filesystem::directory_iterator(local_backups_dir))
LocalServiceInfo service_info = get_service_info(server, service); {
if (!SIvalid(service_info)) { if (!entry.is_regular_file())
error << "Service " << service << " is not valid" << std::endl; continue;
return;
}
std::string template_name = service_info.template_name;
std::string local_backups_dir = gConfig().get_local_backup_path();
if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) {
error << "Error: Local backups directory not found: " << local_backups_dir << std::endl;
return;
}
for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) {
if (!entry.is_regular_file()) continue;
std::string filename = entry.path().filename().string(); std::string filename = entry.path().filename().string();
shared_commands::cBackupFileName backup_details(filename); shared_commands::cBackupFileName backup_details(filename);
if (backup_details.is_valid()) { if (backup_details.is_valid())
if (backup_details.get_template_name() == template_name) { if (match_service.empty() || backup_details.get_service() == match_service)
backups.push_back(backup_details); if (match_template_name.empty() || backup_details.get_template_name() == match_template_name)
} backups.push_back(backup_details);
}
} }
// sort backups by datetime // sort backups by datetime
std::sort(backups.begin(), backups.end(), [](const shared_commands::cBackupFileName& a, const shared_commands::cBackupFileName& b) { std::sort(backups.begin(), backups.end(), [](const shared_commands::cBackupFileName &a, const shared_commands::cBackupFileName &b)
return a.get_datetime() > b.get_datetime(); { return a.get_datetime() > b.get_datetime(); });
});
// print most recent backup for each {host,service} pair return backups;
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()) { int restoredata_handler(const CommandContext &ctx)
unique_backups[key] = backup.get_filename(); {
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;
} }
} }
for (const auto& [key, value] : unique_backups) {
rawout << value << std::endl; 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 } // namespace dropshell

View File

@ -81,6 +81,21 @@ namespace dropshell
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_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); 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
#endif #endif

View File

@ -41,45 +41,43 @@ 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())
{ {
error << "Invalid server: " << server << std::endl; error << "Invalid server: " << server << std::endl;
return false; // should never hit this. return false; // should never hit this.
} }
// 2. Check if service directory exists on server // 2. Check if service directory exists on server
if (!server_env.check_remote_dir_exists(remotepath::service(server, service))) if (!server_env.check_remote_dir_exists(remotepath::service(server, service)))
{ {
error << "Service is not installed: " << service << std::endl; error << "Service is not installed: " << service << std::endl;
return true; // Nothing to uninstall return true; // Nothing to uninstall
} }
// 3. Run uninstall script if it exists // 3. Run uninstall script if it exists
std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh"; std::string uninstall_script = remotepath::service_template(server, service) + "/uninstall.sh";
if (!server_env.run_remote_template_command(service, "uninstall", {}, silent, {})) if (!server_env.run_remote_template_command(service, "uninstall", {}, false, {}))
if (!silent)
warning << "Uninstall script failed, but continuing with directory removal" << std::endl; warning << "Uninstall script failed, but continuing with directory removal" << std::endl;
// 4. Remove the service directory from the server, running in a docker container as root. // 4. Remove the service directory from the server, running in a docker container as root.
if (server_env.remove_remote_dir(remotepath::service(server, service), silent)) if (server_env.remove_remote_dir(remotepath::service(server, service), false))
{ {
ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall"); ASSERT(!server_env.check_remote_dir_exists(remotepath::service(server, service)), "Service directory still found on server after uninstall");
if (!silent)
info << "Removed remote service directory " << remotepath::service(server, service) << std::endl; info << "Removed remote service directory " << remotepath::service(server, service) << std::endl;
} }
else if (!silent) else
warning << "Failed to remove remote service directory" << std::endl; warning << "Failed to remove remote service directory" << std::endl;
if (!silent)
info << "Completed service " << service << " uninstall on " << server << std::endl; info << "Completed service " << service << " uninstall on " << server << std::endl;
return true; return true;
} }
} // namespace shared_commands
int uninstall_handler(const CommandContext &ctx) int uninstall_handler(const CommandContext &ctx)
{ {
@ -98,14 +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

@ -121,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;
} }
@ -135,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;
} }

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