From 7c9a45edf5227692a458bc0fc7a4e16ba2966ed8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 May 2025 19:27:17 +1200 Subject: [PATCH 01/10] . --- templates/squashkiwi/nuke.sh | 11 +++++++++++ templates/squashkiwi/uninstall.sh | 3 --- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 templates/squashkiwi/nuke.sh diff --git a/templates/squashkiwi/nuke.sh b/templates/squashkiwi/nuke.sh new file mode 100644 index 0000000..f70d2fa --- /dev/null +++ b/templates/squashkiwi/nuke.sh @@ -0,0 +1,11 @@ +#!/bin/bash +source "${AGENT_PATH}/_common.sh" +_check_required_env_vars "LOCAL_DATA_FOLDER" + +# remove the local data folder +if [ -d "${LOCAL_DATA_FOLDER}" ]; then + rm -rf ${LOCAL_DATA_FOLDER} +fi + +echo "Nuke of ${CONTAINER_NAME} complete" + diff --git a/templates/squashkiwi/uninstall.sh b/templates/squashkiwi/uninstall.sh index 41f87f8..f324a25 100644 --- a/templates/squashkiwi/uninstall.sh +++ b/templates/squashkiwi/uninstall.sh @@ -12,8 +12,5 @@ _remove_container $CONTAINER_NAME || _die "Failed to remove container ${CONTAINE _is_container_running && _die "Couldn't stop existing container" _is_container_exists && _die "Couldn't remove existing container" -# remove the image -docker rmi "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" || echo "Failed to remove image $IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" - echo "Uninstallation of ${CONTAINER_NAME} complete." echo "Local data folder ${LOCAL_DATA_FOLDER} still in place." From e727fc518ff8881671f7484b5fd969c3bd672009 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 May 2025 20:11:36 +1200 Subject: [PATCH 02/10] Pipe through temp folder to scripts. --- src/server_env_manager.hpp | 1 + src/service_runner.cpp | 57 +++++++++++++------ src/service_runner.hpp | 11 ++++ src/utils/directories.cpp | 6 ++ src/utils/directories.hpp | 5 +- src/utils/utils.cpp | 14 +++++ src/utils/utils.hpp | 2 + .../dropshell-agent/shared/_autocommands.sh | 24 ++++++++ templates/test_template.sh | 10 ---- 9 files changed, 103 insertions(+), 27 deletions(-) diff --git a/src/server_env_manager.hpp b/src/server_env_manager.hpp index 8e1ac20..e4f4bf8 100644 --- a/src/server_env_manager.hpp +++ b/src/server_env_manager.hpp @@ -64,6 +64,7 @@ class server_env_manager { std::string get_SSH_PORT() const { return get_variable("SSH_PORT"); } std::string get_DROPSHELL_DIR() const { return get_variable("DROPSHELL_DIR"); } bool is_valid() const { return mValid; } + std::string get_server_name() const { return mServerName; } // helper functions public: diff --git a/src/service_runner.cpp b/src/service_runner.cpp index 16b687b..9acb65e 100644 --- a/src/service_runner.cpp +++ b/src/service_runner.cpp @@ -556,8 +556,10 @@ bool service_runner::restore(std::string backup_file, bool silent) std::cerr << "Failed to copy backup file from server" << std::endl; return false; } - mServerEnv.run_remote_template_command(mService, "restore", {remote_backup_file_path}, silent); - } + + cRemoteTempFolder remote_temp_folder(mServerEnv); + mServerEnv.run_remote_template_command(mService, "restore", {remote_backup_file_path, remote_temp_folder.path()}, silent); + } // dtor of remote_temp_folder will clean up the temp folder on the server // healthcheck the service std::cout << "3) Healthchecking service..." << std::endl; @@ -652,20 +654,22 @@ bool service_runner::backup(bool silent) { // 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)); - // Run backup script - if (!mServerEnv.run_remote_template_command(mService, command, {remote_backup_file_path}, silent)) { - std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; - return false; - } + { // Run backup script + cRemoteTempFolder remote_temp_folder(mServerEnv); + if (!mServerEnv.run_remote_template_command(mService, command, {remote_backup_file_path, remote_temp_folder.path()}, silent)) { + std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; + return false; + } - // Copy backup file from server to local - std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + - mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + - quote(remote_backup_file_path) + " " + quote(local_backup_file_path) + (silent ? " > /dev/null 2>&1" : ""); - if (!mServerEnv.execute_local_command(scp_cmd)) { - std::cerr << "Failed to copy backup file from server" << std::endl; - return false; - } + // Copy backup file from server to local + std::string scp_cmd = "scp -P " + mServerEnv.get_SSH_PORT() + " " + + mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + + quote(remote_backup_file_path) + " " + quote(local_backup_file_path) + (silent ? " > /dev/null 2>&1" : ""); + if (!mServerEnv.execute_local_command(scp_cmd)) { + std::cerr << "Failed to copy backup file from server" << std::endl; + return false; + } + } // dtor of remote_temp_folder will clean up the temp folder on the server if (!silent) { std::cout << "Backup created successfully. Restore with:"< #include #include +#include + namespace dropshell { void maketitle(const std::string& title) { @@ -292,6 +294,18 @@ std::string replace_with_environment_variables_like_bash(std::string str) { return result; } +std::string random_alphanumeric_string(int length) +{ + static std::mt19937 generator(std::random_device{}()); + static const std::string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::uniform_int_distribution<> distribution(0, chars.size() - 1); + std::string random_string; + for (int i = 0; i < length; ++i) { + random_string += chars[distribution(generator)]; + } + + return random_string; +} std::string requote(std::string str) { return quote(trim(dequote(trim(str)))); diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index ab8ce7e..91c5869 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -38,4 +38,6 @@ int count_substring(const std::string &substring, const std::string &text); std::string replace_with_environment_variables_like_bash(std::string str); +std::string random_alphanumeric_string(int length); + } // namespace dropshell \ No newline at end of file diff --git a/templates/dropshell-agent/shared/_autocommands.sh b/templates/dropshell-agent/shared/_autocommands.sh index c181ccb..bb2e36f 100644 --- a/templates/dropshell-agent/shared/_autocommands.sh +++ b/templates/dropshell-agent/shared/_autocommands.sh @@ -1,6 +1,27 @@ #!/bin/bash +_autocommandrun_volume() { + command="$1" + value="$2" + backup_file="$3" + temp_path="$4" +} + +_autocommandrun_path() { + command="$1" + value="$2" + backup_file="$3" + temp_path="$4" +} + +_autocommandrun_file() { + command="$1" + value="$2" + backup_file="$3" + temp_path="$4" +} + _autocommandrun() { command="$1" key="$2" @@ -13,12 +34,15 @@ _autocommandrun() { case "$key" in volume) echo "Volume: $value" + _autocommandrun_volume "$command" "$value" "$backup_file" "$temp_path" ;; path) echo "Path: $value" + _autocommandrun_path "$command" "$value" "$backup_file" "$temp_path" ;; file) echo "File: $value" + _autocommandrun_file "$command" "$value" "$backup_file" "$temp_path" ;; *) _die "Unknown key $key passed to auto${command}. We only support volume, path and file." diff --git a/templates/test_template.sh b/templates/test_template.sh index afd38d1..38c0e7c 100755 --- a/templates/test_template.sh +++ b/templates/test_template.sh @@ -27,16 +27,6 @@ fi title "Checking template $TEMPLATE" - -HASH1=$(ds hash "$SCRIPT_DIR/$TEMPLATE") -HASH2=$(ds hash "/opt/dropshell/templates/$TEMPLATE") - -if [ "$HASH1" != "$HASH2" ]; then - echo "Template $TEMPLATE is out of date" - echo "Need to run build.sh, and install locally." - exit 1 -fi - SERVICE_NAME="test-$TEMPLATE" title "Creating service" From 63490d9ce3fbe446310b45ab38a537a4311fd85f Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 May 2025 21:12:50 +1200 Subject: [PATCH 03/10] Tidy --- templates/caddy/backup.sh | 4 +- templates/caddy/install.sh | 4 +- templates/caddy/nuke.sh | 4 +- templates/caddy/restore.sh | 4 +- .../dropshell-agent/shared/_autocommands.sh | 150 +++++++++++++----- templates/example-nginx/backup.sh | 2 +- templates/example-nginx/install.sh | 6 +- templates/example-nginx/nuke.sh | 3 +- templates/example-nginx/restore.sh | 2 +- templates/simple-object-storage/backup.sh | 4 +- templates/simple-object-storage/install.sh | 2 - templates/simple-object-storage/nuke.sh | 4 +- templates/simple-object-storage/restore.sh | 4 +- templates/squashkiwi/backup.sh | 2 +- templates/squashkiwi/nuke.sh | 5 +- templates/squashkiwi/restore.sh | 2 +- 16 files changed, 127 insertions(+), 75 deletions(-) diff --git a/templates/caddy/backup.sh b/templates/caddy/backup.sh index 9d38bcf..810b024 100644 --- a/templates/caddy/backup.sh +++ b/templates/caddy/backup.sh @@ -4,9 +4,7 @@ _check_required_env_vars _stop_container "$CONTAINER_NAME" -if ! autobackup volume=$DATA_VOLUME volume=$CONFIG_VOLUME $1 $2; then - _die "Failed to create backup" -fi +autobackup "$1" "$2" "volume=$DATA_VOLUME" "volume=$CONFIG_VOLUME" || _die "Failed to create backup" _start_container "$CONTAINER_NAME" diff --git a/templates/caddy/install.sh b/templates/caddy/install.sh index 706067d..1ad9eba 100644 --- a/templates/caddy/install.sh +++ b/templates/caddy/install.sh @@ -2,9 +2,7 @@ source "${AGENT_PATH}/_common.sh" _check_required_env_vars "CONTAINER_NAME" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAGE_TAG" "DATA_VOLUME" "CONFIG_VOLUME" "CONFIG_PATH" -if ! autocreate volume=$DATA_VOLUME volume=$CONFIG_VOLUME; then - _die "Failed to autocreate volumes and paths" -fi +autocreate "volume=$DATA_VOLUME" "volume=$CONFIG_VOLUME" || _die "Failed to autocreate volumes $DATA_VOLUME and $CONFIG_VOLUME" _check_docker_installed || _die "Docker test failed, aborting installation..." diff --git a/templates/caddy/nuke.sh b/templates/caddy/nuke.sh index 02f0814..5495b27 100644 --- a/templates/caddy/nuke.sh +++ b/templates/caddy/nuke.sh @@ -8,8 +8,6 @@ _check_required_env_vars # any docker volumes and any custom local data folders. -if ! autonuke volume=$DATA_VOLUME volume=$CONFIG_VOLUME; then - _die "Failed to nuke" -fi +autonuke "volume=$DATA_VOLUME" "volume=$CONFIG_VOLUME" || _die "Failed to nuke" echo "Nuking of ${CONTAINER_NAME} complete." diff --git a/templates/caddy/restore.sh b/templates/caddy/restore.sh index cfc10ba..e5c7993 100644 --- a/templates/caddy/restore.sh +++ b/templates/caddy/restore.sh @@ -9,9 +9,7 @@ _check_required_env_vars bash ./uninstall.sh || _die "Failed to uninstall service before restore" # restore data from backup file -if ! autorestore volume=$DATA_VOLUME volume=$CONFIG_VOLUME "$1" "$2"; then - _die "Failed to restore data from backup file" -fi +autorestore "$1" "$2" "volume=$DATA_VOLUME" "volume=$CONFIG_VOLUME" || _die "Failed to restore data from backup file" # reinstall service bash ./install.sh || _die "Failed to reinstall service after restore" diff --git a/templates/dropshell-agent/shared/_autocommands.sh b/templates/dropshell-agent/shared/_autocommands.sh index bb2e36f..4e92163 100644 --- a/templates/dropshell-agent/shared/_autocommands.sh +++ b/templates/dropshell-agent/shared/_autocommands.sh @@ -3,49 +3,77 @@ _autocommandrun_volume() { command="$1" - value="$2" - backup_file="$3" - temp_path="$4" + volume_name="$2" + + case "$command" in + create) + echo "Creating volume ${volume_name}" + docker volume create ${volume_name} + ;; + nuke) + echo "Nuking volume ${volume_name}" + docker volume rm ${volume_name} + ;; + backup) + local backup_file="$3" + echo "Backing up volume ${volume_name}" + docker run --rm -v ${volume_name}:/volume -v ${temp_path}:/backup alpine tar -czvf /backup/volume.tar.gz -C /volume . + ;; + restore) + local backup_file="$3" + echo "Restoring volume ${volume_name}" + docker volume rm ${volume_name} + docker volume create ${volume_name} + docker run --rm -v ${volume_name}:/volume -v ${temp_path}:/backup alpine tar -xzvf /backup/volume.tar.gz -C /volume --strip-components=1 + ;; + esac } _autocommandrun_path() { command="$1" - value="$2" - backup_file="$3" - temp_path="$4" + path="$2" + + case "$command" in + create) + echo "Creating path ${path}" + mkdir -p ${path} + ;; + nuke) + echo "Nuking path ${path}" + rm -rf ${path} + ;; + backup) + local backup_file="$3" + echo "Backing up path ${path}" + tar -czvf ${backup_file} -C ${path} . + ;; + restore) + local backup_file="$3" + echo "Restoring path ${path}" + tar -xzvf ${backup_file} -C ${path} --strip-components=1 + ;; + esac } _autocommandrun_file() { command="$1" value="$2" - backup_file="$3" - temp_path="$4" -} -_autocommandrun() { - command="$1" - key="$2" - value="$3" - - # only passed through if command is backup or restore. - backup_file="$4" - temp_path="$5" - - case "$key" in - volume) - echo "Volume: $value" - _autocommandrun_volume "$command" "$value" "$backup_file" "$temp_path" + case "$command" in + create) ;; - path) - echo "Path: $value" - _autocommandrun_path "$command" "$value" "$backup_file" "$temp_path" + nuke) + rm -f ${value} ;; - file) - echo "File: $value" - _autocommandrun_file "$command" "$value" "$backup_file" "$temp_path" + backup) + local backup_file="$3" + echo "Backing up file ${value}" + cp ${value} ${backup_file} ;; - *) - _die "Unknown key $key passed to auto${command}. We only support volume, path and file." + restore) + local backup_file="$3" + echo "Restoring file ${value}" + cp ${backup_file} ${value} ;; esac } @@ -59,19 +87,20 @@ _autocommandparse() { # value is the path or volume name. # we iterate over the key=value arguments, and for each we call: - # autorun + # autorun local command="$1" shift + local temp_path="$2" + shift + # Extract the backup file and temp path (last two arguments) local args=("$@") local arg_count=${#args[@]} - local backup_file="${args[$arg_count-2]}" - local temp_path="${args[$arg_count-1]}" # Process all key=value pairs - for ((i=0; i<$arg_count-2; i++)); do + for ((i=0; i<$arg_count; i++)); do local pair="${args[$i]}" # Skip if not in key=value format @@ -81,30 +110,71 @@ _autocommandparse() { local key="${pair%%=*}" local value="${pair#*=}" - + + local bfile="${temp_path}/${key}_${value}.tgz" + # Key must be one of volume, path or file - _autocommandrun "$command" "$key" "$value" "$backup_file" "$temp_path" + case "$key" in + volume) + _autocommandrun_volume "$command" "$value" "$bfile" + ;; + path) + _autocommandrun_path "$command" "$value" "$bfile" + ;; + file) + _autocommandrun_file "$command" "$value" "$bfile" + ;; + *) + _die "Unknown key $key passed to auto${command}. We only support volume, path and file." + ;; + esac done } autocreate() { - _autocommandparse create "$@" "-" "-" + _autocommandparse create "-" "$@" } autonuke() { - _autocommandparse nuke "$@" "-" "-" + _autocommandparse nuke "-" "$@" } autobackup() { - _autocommandparse backup "$@" + local backup_file="$1" + shift + local temp_path="$1" + shift + + [ -f "$backup_file" ] || _die "Backup file $backup_file does not exist" + [ -d "$temp_path" ] || _die "Temp path $temp_path does not exist" + + local backup_temp_path="$temp_path/backup" + + mkdir -p "$backup_temp_path" + _autocommandparse backup "$backup_temp_path" "$@" + + tar zcvf "$backup_file" -C "$backup_temp_path" . } autorestore() { - _autocommandparse restore "$@" + local backup_file="$1" + shift + local temp_path="$1" + shift + + [ -f "$backup_file" ] || _die "Backup file $backup_file does not exist" + [ -d "$temp_path" ] || _die "Temp path $temp_path does not exist" + + local restore_temp_path="$temp_path/restore" + + mkdir -p "$restore_temp_path" + tar zxvf "$backup_file" -C "$restore_temp_path" --strip-components=1 + + _autocommandparse restore "$restore_temp_path" "$@" } diff --git a/templates/example-nginx/backup.sh b/templates/example-nginx/backup.sh index 104ce1f..46e9593 100644 --- a/templates/example-nginx/backup.sh +++ b/templates/example-nginx/backup.sh @@ -5,6 +5,6 @@ _check_required_env_vars "LOCAL_DATA_FOLDER" # Nginx Example Backup Script # hot backup is fine for nginx website content. -autobackup "path=${LOCAL_DATA_FOLDER}" $1 $2 || _die "Failed to create backup" +autobackup "$1" "$2" "path=${LOCAL_DATA_FOLDER}" || _die "Failed to create backup" echo "Backup complete" diff --git a/templates/example-nginx/install.sh b/templates/example-nginx/install.sh index e93687b..d0270a9 100644 --- a/templates/example-nginx/install.sh +++ b/templates/example-nginx/install.sh @@ -5,11 +5,7 @@ _check_required_env_vars "LOCAL_DATA_FOLDER" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAG # Nginx Example Install Script # Ensure local data folder exists -if [ ! -d "${LOCAL_DATA_FOLDER}" ]; then - echo "Creating local data folder ${LOCAL_DATA_FOLDER}..." - mkdir -p "${LOCAL_DATA_FOLDER}" - chmod 777 "${LOCAL_DATA_FOLDER}" -fi +autocreate "path=${LOCAL_DATA_FOLDER}" echo "Checking Docker installation..." _check_docker_installed || _die "Docker test failed, aborting installation..." diff --git a/templates/example-nginx/nuke.sh b/templates/example-nginx/nuke.sh index dddb809..09ae539 100644 --- a/templates/example-nginx/nuke.sh +++ b/templates/example-nginx/nuke.sh @@ -8,7 +8,6 @@ _check_required_env_vars "LOCAL_DATA_FOLDER" "CONTAINER_NAME" # Call uninstall script first ./uninstall.sh -echo "Removing local data folder ${LOCAL_DATA_FOLDER}..." -rm -rf $LOCAL_DATA_FOLDER || _die "Failed to remove local data folder ${LOCAL_DATA_FOLDER}" +autonuke "path=${LOCAL_DATA_FOLDER}" || _die "Failed to nuke ${LOCAL_DATA_FOLDER}" echo "Nuke complete for service ${CONTAINER_NAME}." diff --git a/templates/example-nginx/restore.sh b/templates/example-nginx/restore.sh index c6f6902..fca14ee 100644 --- a/templates/example-nginx/restore.sh +++ b/templates/example-nginx/restore.sh @@ -9,7 +9,7 @@ BACKUP_FILE="$1" echo "Uninstalling service before restore..." bash ./uninstall.sh || _die "Failed to uninstall service before restore" -autorestore "path=${LOCAL_DATA_FOLDER}" $1 $2 || _die "Failed to restore data folder from backup" +autorestore "$1" "$2" "path=${LOCAL_DATA_FOLDER}" || _die "Failed to restore data folder from backup" echo "Restore complete. Reinstalling service..." bash ./install.sh || _die "Failed to reinstall service after restore" diff --git a/templates/simple-object-storage/backup.sh b/templates/simple-object-storage/backup.sh index 5d7f145..56dad4c 100644 --- a/templates/simple-object-storage/backup.sh +++ b/templates/simple-object-storage/backup.sh @@ -1,6 +1,6 @@ #!/bin/bash source "${AGENT_PATH}/_common.sh" -_check_required_env_vars +_check_required_env_vars "VOLUME_NAME" # Simple Object Storage Backup Script # Creates a backup tarball of the volume contents. @@ -8,6 +8,6 @@ _check_required_env_vars # HOT backup is fine for simple-object-storage -autobackup "volume=${VOLUME_NAME}" $1 $2 || _die "Failed to create backup" +autobackup "$1" "$2" "volume=${VOLUME_NAME}" || _die "Failed to create backup" echo "Backup complete: ${BACKUP_FILE}" diff --git a/templates/simple-object-storage/install.sh b/templates/simple-object-storage/install.sh index cb79fff..f0fc364 100644 --- a/templates/simple-object-storage/install.sh +++ b/templates/simple-object-storage/install.sh @@ -5,8 +5,6 @@ _check_required_env_vars # Simple Object Storage Install Script # Pulls image, creates volume/config, starts container. - - autocreate "volume=${VOLUME_NAME}" # check can pull image on remote host and exit if fails diff --git a/templates/simple-object-storage/nuke.sh b/templates/simple-object-storage/nuke.sh index 00053df..9caebee 100644 --- a/templates/simple-object-storage/nuke.sh +++ b/templates/simple-object-storage/nuke.sh @@ -6,4 +6,6 @@ _check_required_env_vars # Removes container AND volume. -autonuke "volume=${VOLUME_NAME}" +autonuke "volume=${VOLUME_NAME}" || _die "Failed to nuke volume ${VOLUME_NAME}" + +echo "Nuke complete for service ${CONTAINER_NAME}." diff --git a/templates/simple-object-storage/restore.sh b/templates/simple-object-storage/restore.sh index 01d0989..20c47ec 100644 --- a/templates/simple-object-storage/restore.sh +++ b/templates/simple-object-storage/restore.sh @@ -12,9 +12,7 @@ bash ./uninstall.sh || _die "Failed to uninstall service before restore" echo "Restoring data for ${CONTAINER_NAME} from ${BACKUP_FILE}..." -if ! autorestore "volume=${VOLUME_NAME}" "$1" "$2"; then - _die "Failed to restore data from backup file" -fi +autorestore "$1" "$2" "volume=${VOLUME_NAME}" || _die "Failed to restore data from backup file" echo "Restore complete. Reinstalling service..." diff --git a/templates/squashkiwi/backup.sh b/templates/squashkiwi/backup.sh index e3b4a8d..028fdad 100644 --- a/templates/squashkiwi/backup.sh +++ b/templates/squashkiwi/backup.sh @@ -5,7 +5,7 @@ _check_required_env_vars "CONTAINER_NAME" "LOCAL_DATA_FOLDER" # Stop container before backup _stop_container "$CONTAINER_NAME" -autobackup "path=${LOCAL_DATA_FOLDER}" $1 $2 || _die "Failed to create backup" +autobackup "$1" "$2" "path=${LOCAL_DATA_FOLDER}" || _die "Failed to create backup" # Start container after backup _start_container "$CONTAINER_NAME" diff --git a/templates/squashkiwi/nuke.sh b/templates/squashkiwi/nuke.sh index f70d2fa..d3e4cc0 100644 --- a/templates/squashkiwi/nuke.sh +++ b/templates/squashkiwi/nuke.sh @@ -2,10 +2,7 @@ source "${AGENT_PATH}/_common.sh" _check_required_env_vars "LOCAL_DATA_FOLDER" -# remove the local data folder -if [ -d "${LOCAL_DATA_FOLDER}" ]; then - rm -rf ${LOCAL_DATA_FOLDER} -fi +autonuke "path=${LOCAL_DATA_FOLDER}" || _die "Failed to nuke ${LOCAL_DATA_FOLDER}" echo "Nuke of ${CONTAINER_NAME} complete" diff --git a/templates/squashkiwi/restore.sh b/templates/squashkiwi/restore.sh index 78744ca..2623a49 100644 --- a/templates/squashkiwi/restore.sh +++ b/templates/squashkiwi/restore.sh @@ -10,7 +10,7 @@ _check_required_env_vars "CONTAINER_NAME" "LOCAL_DATA_FOLDER" # # Stop container before backup bash ./uninstall.sh || _die "Failed to uninstall service before restore" -autorestore "path=${LOCAL_DATA_FOLDER}" $1 $2 || _die "Failed to restore data folder from backup" +autorestore "$1" "$2" "path=${LOCAL_DATA_FOLDER}" || _die "Failed to restore data folder from backup" # reinstall service bash ./install.sh || _die "Failed to reinstall service after restore" From 1776a7e45fc4a61019040bb2ee109d14e5cba3f7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 May 2025 21:43:07 +1200 Subject: [PATCH 04/10] . --- src/service_runner.cpp | 14 +++-- .../dropshell-agent/shared/_autocommands.sh | 53 ++++++++++++------- templates/test_template.sh | 2 +- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/service_runner.cpp b/src/service_runner.cpp index 9acb65e..61668cb 100644 --- a/src/service_runner.cpp +++ b/src/service_runner.cpp @@ -491,6 +491,12 @@ bool service_runner::restore(std::string backup_file, bool silent) } std::string local_backups_dir = gConfig().get_local_backup_path(); + + if (backup_file == "latest") { + // get the latest backup file from the server + backup_file = get_latest_backup_file(mServer, mService); + } + std::string local_backup_file_path = (std::filesystem::path(local_backups_dir) / backup_file).string(); if (! std::filesystem::exists(local_backup_file_path)) { @@ -520,14 +526,6 @@ bool service_runner::restore(std::string backup_file, bool silent) std::cout << "Restoring " << nicedate << " backup of " << backup_template_name << " taken from "<> confirm; - if (confirm != 'y') { - std::cout << "Restore cancelled." << std::endl; - return false; - } // run the restore script std::cout << "OK, here goes..." << std::endl; diff --git a/templates/dropshell-agent/shared/_autocommands.sh b/templates/dropshell-agent/shared/_autocommands.sh index 4e92163..ff19ba3 100644 --- a/templates/dropshell-agent/shared/_autocommands.sh +++ b/templates/dropshell-agent/shared/_autocommands.sh @@ -1,5 +1,7 @@ #!/bin/bash +MYID=$(id -u) +MYGRP=$(id -g) _autocommandrun_volume() { command="$1" @@ -15,16 +17,16 @@ _autocommandrun_volume() { docker volume rm ${volume_name} ;; backup) - local backup_file="$3" + local backup_folder="$3" echo "Backing up volume ${volume_name}" - docker run --rm -v ${volume_name}:/volume -v ${temp_path}:/backup alpine tar -czvf /backup/volume.tar.gz -C /volume . + docker run --rm -v ${volume_name}:/volume -v ${backup_folder}:/backup debian bash -c "tar -czvf /backup/backup.tgz -C /volume . && chown -R $MYID:$MYGRP /backup" ;; restore) - local backup_file="$3" + local backup_folder="$3" echo "Restoring volume ${volume_name}" docker volume rm ${volume_name} docker volume create ${volume_name} - docker run --rm -v ${volume_name}:/volume -v ${temp_path}:/backup alpine tar -xzvf /backup/volume.tar.gz -C /volume --strip-components=1 + docker run --rm -v ${volume_name}:/volume -v ${backup_folder}:/backup debian bash -c "tar -xzvf /backup/backup.tgz -C /volume --strip-components=1" ;; esac } @@ -43,14 +45,14 @@ _autocommandrun_path() { rm -rf ${path} ;; backup) - local backup_file="$3" + local backup_folder="$3" echo "Backing up path ${path}" - tar -czvf ${backup_file} -C ${path} . + tar -czvf ${backup_folder}/backup.tgz -C ${path} . ;; restore) - local backup_file="$3" + local backup_folder="$3" echo "Restoring path ${path}" - tar -xzvf ${backup_file} -C ${path} --strip-components=1 + tar -xzvf ${backup_folder}/backup.tgz -C ${path} --strip-components=1 ;; esac } @@ -66,14 +68,17 @@ _autocommandrun_file() { rm -f ${value} ;; backup) - local backup_file="$3" + local backup_folder="$3" echo "Backing up file ${value}" - cp ${value} ${backup_file} + # get filename from path + local filename=$(basename ${value}) + cp ${value} ${backup_folder}/${filename} ;; restore) - local backup_file="$3" + local backup_folder="$3" echo "Restoring file ${value}" - cp ${backup_file} ${value} + local filename=$(basename ${value}) + cp ${backup_folder}/${filename} ${value} ;; esac } @@ -92,9 +97,11 @@ _autocommandparse() { local command="$1" shift - local temp_path="$2" + local temp_path="$1" shift + echo "autocommandparse: command=$command temp_path=$temp_path" + # Extract the backup file and temp path (last two arguments) local args=("$@") local arg_count=${#args[@]} @@ -111,18 +118,27 @@ _autocommandparse() { local key="${pair%%=*}" local value="${pair#*=}" - local bfile="${temp_path}/${key}_${value}.tgz" + # create backup folder unique to key/value. + local bfolder="${key}_${value}" + + # remove any non-alphanumeric characters, that aren't dash or underscore from the bfile + targetpath="" + if [ ! "$temp_path" == "-" ]; then + bfolder=$(echo "$bfolder" | tr -cd '[:alnum:]_-') + mkdir -p ${temp_path}/${bfolder} + targetpath="${temp_path}/${bfolder}" + fi # Key must be one of volume, path or file case "$key" in volume) - _autocommandrun_volume "$command" "$value" "$bfile" + _autocommandrun_volume "$command" "$value" "$targetpath" ;; path) - _autocommandrun_path "$command" "$value" "$bfile" + _autocommandrun_path "$command" "$value" "$targetpath" ;; file) - _autocommandrun_file "$command" "$value" "$bfile" + _autocommandrun_file "$command" "$value" "$targetpath" ;; *) _die "Unknown key $key passed to auto${command}. We only support volume, path and file." @@ -148,12 +164,13 @@ autobackup() { local temp_path="$1" shift - [ -f "$backup_file" ] || _die "Backup file $backup_file does not exist" + [ -f "$backup_file" ] && _die "Backup file $backup_file already exists" [ -d "$temp_path" ] || _die "Temp path $temp_path does not exist" local backup_temp_path="$temp_path/backup" mkdir -p "$backup_temp_path" + echo "_autocommandparse [backup] [$backup_temp_path] [$@]" _autocommandparse backup "$backup_temp_path" "$@" tar zcvf "$backup_file" -C "$backup_temp_path" . diff --git a/templates/test_template.sh b/templates/test_template.sh index 38c0e7c..4a873e6 100755 --- a/templates/test_template.sh +++ b/templates/test_template.sh @@ -48,7 +48,7 @@ title "Backing up service" ds backup localhost $SERVICE_NAME || die "Failed to backup service" title "Restoring service" -ds restore localhost $SERVICE_NAME || die "Failed to restore service" +ds restore localhost $SERVICE_NAME latest || die "Failed to restore service" title "Checking status" ds status localhost $SERVICE_NAME || die "Failed to check status" From 9d01554b13ed5f8b4b476e58b2c4ce3e2dc0e45d Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 May 2025 21:48:36 +1200 Subject: [PATCH 05/10] Add 'latest' hidden option to restore --- src/main.cpp | 6 ++--- src/service_runner.cpp | 52 +++++++++++++++++++++++++++++++++++++++--- src/service_runner.hpp | 21 ++++++++--------- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b528dbc..b243c9b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -140,7 +140,7 @@ int main(int argc, char* argv[]) { gConfig().save_config(false); std::string config_file = localfile::dropshell_json(); - if (!edit_file(config_file) || !std::filesystem::exists(config_file)) + if (!service_runner::edit_file(config_file) || !std::filesystem::exists(config_file)) return die("Error: Failed to edit config file."); gConfig().load_config(); @@ -208,13 +208,13 @@ int main(int argc, char* argv[]) { if (cmd == "ssh" && argc < 4) { if (argc < 3) return die("Error: ssh requires a server name and optionally service name"); - interactive_ssh(argv[2], "bash"); + service_runner::interactive_ssh(argv[2], "bash"); return 0; } if (cmd == "edit" && argc < 4) { ASSERT_MSG(argc>=3, "Error: logic error!"); - edit_server(safearg(argc,argv,2)); + service_runner::edit_server(safearg(argc,argv,2)); return 0; } diff --git a/src/service_runner.cpp b/src/service_runner.cpp index 61668cb..f9f5bba 100644 --- a/src/service_runner.cpp +++ b/src/service_runner.cpp @@ -379,7 +379,7 @@ std::string service_runner::healthmark() return HealthStatus2String(status); } -void interactive_ssh(const std::string & server_name, const std::string & command) { +void service_runner::interactive_ssh(const std::string & server_name, const std::string & command) { std::string serverpath = localpath::server(server_name); if (serverpath.empty()) { std::cerr << "Error: Server not found: " << server_name << std::endl; @@ -409,7 +409,7 @@ void interactive_ssh(const std::string & server_name, const std::string & comman exit(EXIT_FAILURE); } -void edit_server(const std::string &server_name) +void service_runner::edit_server(const std::string &server_name) { std::string serverpath = localpath::server(server_name); if (serverpath.empty()) { @@ -432,7 +432,7 @@ void edit_server(const std::string &server_name) std::cout << aftertext.str() << std::endl; } -bool edit_file(const std::string &file_path) +bool service_runner::edit_file(const std::string &file_path) { // make sure parent directory exists. std::string parent_dir = get_parent(file_path); @@ -698,5 +698,51 @@ std::string cRemoteTempFolder::path() const return mPath; } +// Helper function to get the latest backup file for a given server and service +std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) { + std::string local_backups_dir = gConfig().get_local_backup_path(); + if (local_backups_dir.empty() || !std::filesystem::exists(local_backups_dir)) { + std::cerr << "Error: Local backups directory not found: " << local_backups_dir << std::endl; + return ""; + } + + // Get the template name for this service + LocalServiceInfo info = get_service_info(server, service); + if (info.template_name.empty()) { + std::cerr << "Error: Could not determine template name for service: " << service << std::endl; + return ""; + } + + // Build the expected prefix for backup files + std::string prefix = server + magic_string + info.template_name + magic_string + service + magic_string; + std::string latest_file; + std::string latest_datetime; + + std::cout << "Looking for backup files in " << local_backups_dir << std::endl; + + for (const auto& entry : std::filesystem::directory_iterator(local_backups_dir)) { + if (!entry.is_regular_file()) continue; + std::string filename = entry.path().filename().string(); + if (filename.rfind(prefix, 0) == 0) { // starts with prefix + // Extract the datetime part + size_t dt_start = prefix.size(); + size_t dt_end = filename.find(".tgz", dt_start); + if (dt_end == std::string::npos) continue; + std::string datetime = filename.substr(dt_start, dt_end - dt_start); + std::cout << "Found backup file: " << filename << " with datetime: " << datetime << std::endl; + if (datetime > latest_datetime) { + latest_datetime = datetime; + latest_file = filename; + } + } + } + + if (latest_file.empty()) { + std::cerr << "Error: No backup files found for " << server << ", " << service << std::endl; + } + + std::cout << "Latest backup file: " << latest_file << std::endl; + return latest_file; +} } // namespace dropshell \ No newline at end of file diff --git a/src/service_runner.hpp b/src/service_runner.hpp index 81284a0..6923936 100644 --- a/src/service_runner.hpp +++ b/src/service_runner.hpp @@ -56,10 +56,6 @@ class service_runner { std::string healthtick(); std::string healthmark(); - // get the status of all services on the server - static std::map get_all_services_status(std::string server_name); - static std::string HealthStatus2String(HealthStatus status); - private: // install the service over ssh, using the credentials from server.env (via server_env.hpp), by: // 1. check if the server_name exists, and the service_name refers to a valid template @@ -91,6 +87,16 @@ class service_runner { // edit the service configuration file void edit_service_config(); + + public: + // utility functions + static std::string get_latest_backup_file(const std::string& server, const std::string& service); + static void interactive_ssh(const std::string & server_name, const std::string & command); + static void edit_server(const std::string & server_name); + static bool edit_file(const std::string & file_path); + static std::map get_all_services_status(std::string server_name); + static std::string HealthStatus2String(HealthStatus status); + private: std::string mServer; server_env_manager mServerEnv; @@ -112,13 +118,6 @@ class service_runner { const server_env_manager & mServerEnv; }; - - -// other utility routines (not specific to service_runner) -void interactive_ssh(const std::string & server_name, const std::string & command); -void edit_server(const std::string & server_name); -bool edit_file(const std::string & file_path); - } // namespace dropshell #endif // SERVICE_RUNNER_HPP From ac20fcec3dd777f835f25a4f5189c56c3cd1abae Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 May 2025 22:19:04 +1200 Subject: [PATCH 06/10] Return more error codes as return value of exe. --- src/autocomplete.cpp | 27 ++++++------- src/autocomplete.hpp | 4 +- src/main.cpp | 24 +++++------- src/server_env_manager.cpp | 41 ++++++++++---------- src/servers.cpp | 7 ++-- src/servers.hpp | 2 +- src/templates.cpp | 14 +++---- src/templates.hpp | 2 +- templates/squashkiwi/config/service.env | 2 +- templates/test_template.sh | 50 ++++++++++++++++++++----- 10 files changed, 99 insertions(+), 74 deletions(-) diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp index 0452435..985dbc1 100644 --- a/src/autocomplete.cpp +++ b/src/autocomplete.cpp @@ -9,12 +9,12 @@ #include #include -void dropshell::autocomplete(const std::vector &args) +bool dropshell::autocomplete(const std::vector &args) { if (args.size() < 3) // dropshell autocomplete ??? { autocomplete_list_commands(); - return; + return true; } ASSERT(args.size() >= 3); @@ -27,23 +27,23 @@ void dropshell::autocomplete(const std::vector &args) std::filesystem::directory_iterator dir_iter(std::filesystem::current_path()); for (const auto& entry : dir_iter) std::cout << entry.path().filename().string() << std::endl; - return; + return true; } std::string noargcmds[] = {"templates","autocomplete_list_servers","autocomplete_list_services","autocomplete_list_commands"}; if (std::find(std::begin(noargcmds), std::end(noargcmds), cmd) != std::end(noargcmds)) - return; + return true; if (!dropshell::gConfig().is_config_set()) - return; // can't help without working config. + return false; // can't help without working config. if (args.size()==3) // we have the command but nothing else. dropshell autocomplete command { auto servers = dropshell::get_configured_servers(); for (const auto& server : servers) - std::cout << server.name << std::endl; - return; + std::cout << server.name << std::endl; + return true; } if (args.size()==4) // we have the command and the server. dropshell autocomplete command server @@ -55,13 +55,13 @@ void dropshell::autocomplete(const std::vector &args) auto templates = dropshell::gTemplateManager().get_template_list(); for (const auto& t : templates) std::cout << t << std::endl; - return; + return true; } auto services = dropshell::get_server_services_info(server); for (const auto& service : services) std::cout << service.service_name << std::endl; - return; + return true; } if (args.size()==5) // we have the command and the server and the service. dropshell autocomplete command server service_name @@ -73,17 +73,17 @@ void dropshell::autocomplete(const std::vector &args) std::set backups = dropshell::list_backups(server_name, service_name); for (auto backup : backups) std::cout << backup << std::endl; - return; + return true; } - return; // no more autocompletion possible - don't know what the argument is for. + return false; // no more autocompletion possible - don't know what the argument is for. } // args>5 - no more autocompletion possible - don't know what the argument is for. - return; // catch-all. + return false; // catch-all. } -void dropshell::autocomplete_list_commands() +bool dropshell::autocomplete_list_commands() { std::set commands; dropshell::get_all_used_commands(commands); @@ -100,5 +100,6 @@ void dropshell::autocomplete_list_commands() for (const auto& command : commands) { std::cout << command << std::endl; } + return true; } diff --git a/src/autocomplete.hpp b/src/autocomplete.hpp index 5bd1f6f..8b30147 100644 --- a/src/autocomplete.hpp +++ b/src/autocomplete.hpp @@ -7,9 +7,9 @@ namespace dropshell { - void autocomplete(const std::vector &args); + bool autocomplete(const std::vector &args); - void autocomplete_list_commands(); + bool autocomplete_list_commands(); } // namespace dropshell diff --git a/src/main.cpp b/src/main.cpp index b243c9b..5c1711e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,7 @@ extern const std::string RELEASE_DATE; extern const std::string AUTHOR; extern const std::string LICENSE; -void print_help() { +bool print_help() { std::cout << std::endl; maketitle("DropShell version " + VERSION); std::cout << std::endl; @@ -59,6 +59,7 @@ void print_help() { std::cout << std::endl; std::cout << "Other commands available once initialised." << std::endl; } + return true; } @@ -125,15 +126,11 @@ int main(int argc, char* argv[]) { argvec.push_back(argv[i]); - if (cmd == "autocomplete") { - autocomplete(argvec); - return 0; - } + if (cmd == "autocomplete") + return autocomplete(argvec) ? 0 : 1; - if (cmd == "help" || cmd == "-h" || cmd == "--help" || cmd== "h" || cmd=="halp") { - print_help(); - return 0; - } + if (cmd == "help" || cmd == "-h" || cmd == "--help" || cmd== "h" || cmd=="halp") + return print_help() ? 0 : 1; if (cmd == "edit" && argc < 3) { if (!gConfig().is_config_set()) @@ -190,20 +187,17 @@ int main(int argc, char* argv[]) { if (cmd == "create-template") { if (argc < 3) return die("Error: create-template requires a template name"); - gTemplateManager().create_template(argv[2]); - return 0; + return (gTemplateManager().create_template(argv[2])) ? 0 : 1; } if (cmd == "create-server") { if (argc < 3) return die("Error: create-server requires a server name"); - create_server(argv[2]); - return 0; + return (create_server(argv[2])) ? 0 : 1; } if (cmd == "create-service") { if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service"); - create_service(argv[2], argv[3], argv[4]); - return 0; + return (create_service(argv[2], argv[3], argv[4])) ? 0 : 1; } if (cmd == "ssh" && argc < 4) { diff --git a/src/server_env_manager.cpp b/src/server_env_manager.cpp index 979f8d3..6b914ef 100644 --- a/src/server_env_manager.cpp +++ b/src/server_env_manager.cpp @@ -161,9 +161,21 @@ bool server_env_manager::check_remote_items_exist(const std::vector return true; } +bool EXITSTATUSCHECK(int ret) { + return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute. +} + bool server_env_manager::execute_ssh_command(const sCommand& command, bool allocateTTY) const { std::string full_cmd = construct_ssh_cmd(allocateTTY) + " " + (allocateTTY ? halfquote(command.construct_rawcmd()) : quote(command.construct_safecmd())); - return (system(full_cmd.c_str()) == 0); + int ret = system(full_cmd.c_str()); + if (ret == -1) { + std::cerr << "Error: Failed to execute command: " << full_cmd << std::endl; + // system() failed to execute + return false; + } else { + // Check if the command exited normally and with status 0 + return EXITSTATUSCHECK(ret); + } } bool server_env_manager::execute_ssh_command_and_capture_output(const sCommand& command, std::string &output, bool allocateTTY) const @@ -187,12 +199,8 @@ bool server_env_manager::run_remote_template_command_and_capture_output(const st } bool server_env_manager::execute_local_command(const sCommand& command) { - auto status = system(command.construct_safecmd().c_str()); - if ( WIFEXITED(status) ) { - int returned = WEXITSTATUS(status); - return (returned == 0); - } - return false; + int ret = system(command.construct_safecmd().c_str()); + return EXITSTATUSCHECK(ret); } bool server_env_manager::execute_local_command_interactive(const sCommand &command) @@ -217,19 +225,14 @@ bool server_env_manager::execute_local_command_interactive(const sCommand &comma // If execvp returns, it means an error occurred perror("execvp failed"); exit(EXIT_FAILURE); // Exit child process on error - } else { // Parent process - int status; + int ret; // Wait for the child process to complete - waitpid(pid, &status, 0); + waitpid(pid, &ret, 0); - if (WIFEXITED(status)) { - return (WEXITSTATUS(status) == 0); - } - return false; // Child terminated abnormally + return EXITSTATUSCHECK(ret); } - } bool server_env_manager::execute_local_command_and_capture_output(const sCommand& command, std::string &output) @@ -243,12 +246,8 @@ bool server_env_manager::execute_local_command_and_capture_output(const sCommand while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { output += buffer; } - int status = pclose(pipe); - if ( WIFEXITED(status) ) { - int returned = WEXITSTATUS(status); - return (returned == 0); - } - return false; + int ret = pclose(pipe); + return EXITSTATUSCHECK(ret); } diff --git a/src/servers.cpp b/src/servers.cpp index 579b9a4..551fa45 100644 --- a/src/servers.cpp +++ b/src/servers.cpp @@ -174,14 +174,14 @@ void show_server_details(const std::string& server_name) { } // end of list services } // end of show_server_details -void create_server(const std::string &server_name) +bool create_server(const std::string &server_name) { // 1. check if server name already exists std::string server_existing_dir = localpath::server(server_name); if (!server_existing_dir.empty()) { std::cerr << "Error: Server name already exists: " << server_name << std::endl; std::cerr << "Current server path: " << server_existing_dir << std::endl; - return; + return false; } // 2. create a new directory in the user config directory @@ -189,7 +189,7 @@ void create_server(const std::string &server_name) if (lsdp.empty() || lsdp[0].empty()) { std::cerr << "Error: Local server definition path not found" << std::endl; std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl; - return; + return false; } std::string server_dir = lsdp[0] + "/" + server_name; std::filesystem::create_directory(server_dir); @@ -214,6 +214,7 @@ void create_server(const std::string &server_name) std::cout << "2) test ssh is working: dropshell ssh " << server_name << std::endl; std::cout << "3) install dropshell-agent: dropshell install " << server_name << " dropshell-agent" << std::endl; std::cout << std::endl; + return true; } void get_all_used_commands(std::set &commands) diff --git a/src/servers.hpp b/src/servers.hpp index 2a40400..4afbf66 100644 --- a/src/servers.hpp +++ b/src/servers.hpp @@ -24,7 +24,7 @@ namespace dropshell { void list_servers(); void show_server_details(const std::string& server_name); - void create_server(const std::string& server_name); + bool create_server(const std::string& server_name); void get_all_used_commands(std::set &commands); diff --git a/src/templates.cpp b/src/templates.cpp index a58123b..1a94f5e 100644 --- a/src/templates.cpp +++ b/src/templates.cpp @@ -168,7 +168,7 @@ return source->template_command_exists(template_name, command); } - void template_manager::create_template(const std::string &template_name) const + bool template_manager::create_template(const std::string &template_name) const { // 1. Create a new directory in the user templates directory std::vector local_server_definition_paths = gConfig().get_local_server_definition_paths(); @@ -176,20 +176,20 @@ if (local_server_definition_paths.empty()) { std::cerr << "Error: No local server definition paths found" << std::endl; std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl; - return; + return false; } auto info = get_template_info(template_name); if (info.is_set()) { std::cerr << "Error: Template '" << template_name << "' already exists at " << info.locationID() << std::endl; - return; + return false; } auto local_template_paths = gConfig().get_template_local_paths(); if (local_template_paths.empty()) { std::cerr << "Error: No local template paths found" << std::endl; std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl; - return; + return false; } std::string new_template_path = local_template_paths[0] + "/" + template_name; @@ -200,7 +200,7 @@ auto example_info = gTemplateManager().get_template_info("example-nginx"); if (!example_info.is_set()) { std::cerr << "Error: Example template not found" << std::endl; - return; + return false; } std::string example_template_path = example_info.local_template_path(); @@ -222,7 +222,7 @@ std::string service_env_path = new_template_path + "/config/.template_info.env"; if (!replace_line_in_file(service_env_path, search_string, replacement_line)) { std::cerr << "Error: Failed to replace TEMPLATE= line in the .template_info.env file" << std::endl; - return; + return false; } // 3. Print out the README.txt file and the path @@ -244,7 +244,7 @@ std::cout << std::endl; std::cout << "Template '" << template_name << "' created at " << new_template_path << std::endl; - test_template(new_template_path); + return test_template(new_template_path); } void template_manager::load_sources() diff --git a/src/templates.hpp b/src/templates.hpp index 867d9f8..89986b4 100644 --- a/src/templates.hpp +++ b/src/templates.hpp @@ -84,7 +84,7 @@ class template_manager { template_info get_template_info(const std::string& template_name) const; bool template_command_exists(const std::string& template_name,const std::string& command) const; - void create_template(const std::string& template_name) const; + bool create_template(const std::string& template_name) const; static bool test_template(const std::string& template_path); void list_templates() const; diff --git a/templates/squashkiwi/config/service.env b/templates/squashkiwi/config/service.env index 4e40a47..e9a710a 100644 --- a/templates/squashkiwi/config/service.env +++ b/templates/squashkiwi/config/service.env @@ -2,5 +2,5 @@ # (can also override anything in the _basic.env file in the template to make it specific to this server) HOST_PORT=80 -LOCAL_DATA_FOLDER="${HOME}/.sk" +LOCAL_DATA_FOLDER="/home/dropshell/example-squashkiwi" IMAGE_TAG="latest" diff --git a/templates/test_template.sh b/templates/test_template.sh index 4a873e6..4c86cc3 100755 --- a/templates/test_template.sh +++ b/templates/test_template.sh @@ -1,23 +1,49 @@ #!/bin/bash -# default config should always work for localhost - SCRIPT_DIR=$(dirname "$0") -TEMPLATE="$1" -# make sure TEMPLATE doesn't end with a / -TEMPLATE=$(basename "$TEMPLATE") +# default config should always work for localhost function die() { echo "$1" exit 1 } -function title() { - echo "----------------------------------------" - echo "$1" - echo "----------------------------------------" +function dashes() { + for ((i=0; i<$1; i++)); do + echo -n "-" + done + echo "" } +function centerprint() { + # print $1 centered + local width=$2 + local padding=$(( (width - ${#1}) / 2 )) + for ((i=0; i<$padding; i++)); do + echo -n " " + done + + echo "$1" +} + +function title() { + # determine terminal width + TERMINAL_WIDTH=$(tput cols) + + echo " " + dashes $TERMINAL_WIDTH + centerprint "$1" $TERMINAL_WIDTH + dashes $TERMINAL_WIDTH +} + +# do we have the first argument? +if [ -z "$1" ]; then + echo "Usage: $0