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 b528dbc..49f2a87 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; @@ -40,14 +40,17 @@ void print_help() { std::cout << std::endl; std::cout << std::endl; std::cout << "Service commands: (if no service is specified, all services for the server are affected)" << std::endl; - std::cout << " install SERVER [SERVICE] Install/reinstall/update service(s). Non-destructive." << std::endl; std::cout << " list [SERVER] [SERVICE] List status/details of all servers/server/service." << std::endl; std::cout << " edit [SERVER] [SERVICE] Edit the configuration of dropshell/server/service." << std::endl; - std::cout << " COMMAND SERVER [SERVICE] Run a command on service(s)." << std::endl; std::cout << std::endl; - std::cout << "Standard commands: install, uninstall, backup, restore, start, stop" << std::endl; + std::cout << " install SERVER [SERVICE] Install/reinstall/update service(s). Safe/non-destructive." << std::endl; + std::cout << " uninstall SERVER [SERVICE] Uninstalls the service on the remote server. Leaves data intact." << std::endl; + std::cout << " nuke SERVER SERVICE Nuke the service on the remote server, deleting all remote data." << std::endl; std::cout << std::endl; - std::cout << " ssh SERVER [SERVICE] Launch an interactive shell on a server or service" << std::endl; + std::cout << " COMMAND SERVER [SERVICE] Run a command on service(s), e.g." << std::endl; + std::cout << " backup, restore, start, stop, logs" << std::endl; + std::cout << std::endl; + std::cout << " ssh SERVER SERVICE Launch an interactive shell on a server or service" << std::endl; std::cout << std::endl; std::cout << "Creation commands: (apply to the first local config directory)"< argvec; - for (int i=0; i argvec; + for (int i=0; i=3, "Error: logic error!"); - edit_server(safearg(argc,argv,2)); + service_runner::edit_server(safearg(argc,argv,2)); return 0; } // handle running a command. std::set commands; get_all_used_commands(commands); - commands.merge(std::set{"ssh","edit","_allservicesstatus"}); // handled by service_runner, but not in template_shell_commands. - for (const auto& command : commands) { - if (cmd == command) { - ServerAndServices server_and_services; - if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services)) { - std::cerr << "Error: " << command << " command requires server name and optionally service name" << std::endl; - return 1; - } + commands.merge(std::set{"ssh","edit","_allservicesstatus","fullnuke"}); // handled by service_runner, but not in template_shell_commands. - for (const auto& service_info : server_and_services.servicelist) { + if (commands.count(cmd)) { + std::set safe_commands = {"nuke", "fullnuke"}; + if (safe_commands.count(cmd) && argc < 4) + return die("Error: "+cmd+" requires a server name and service name. For safety, can't run on all services."); + + // get all the services to run the command on. + ServerAndServices server_and_services; + if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services)) + return die("Error: "+cmd+" command requires server name and optionally service name"); + + // run the command on each service. + for (const auto& service_info : server_and_services.servicelist) { + if (!SIvalid(service_info)) + std::cerr<<"Error: Unable to get service information."< additional_args; for (int i=4; i args, bool silent) const { + if (command.empty()) + return sCommand(); + std::string remote_service_template_path = remotepath::service_template(mServerName,service_name); std::string remote_service_config_path = remotepath::service_config(mServerName,service_name); std::string script_path = remote_service_template_path + "/" + command + ".sh"; std::map env_vars; - get_all_service_env_vars(mServerName, service_name, env_vars); + if (!get_all_service_env_vars(mServerName, service_name, env_vars)) { + std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl; + return sCommand(); + } std::string argstr = ""; for (const auto& arg : args) { @@ -126,6 +132,10 @@ sCommand server_env_manager::construct_standard_command_run_cmd(const std::strin } sCommand scommand(remote_service_template_path, "bash " + quote(script_path) + argstr + (silent ? " > /dev/null 2>&1" : ""), env_vars); + + if (scommand.empty()) + std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl; + return scommand; } @@ -161,9 +171,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 @@ -175,6 +197,8 @@ bool server_env_manager::execute_ssh_command_and_capture_output(const sCommand& bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector args, bool silent) const { sCommand scommand = construct_standard_command_run_cmd(service_name, command, args, silent); + if (scommand.get_command_to_run().empty()) + return false; bool allocateTTY = (command=="ssh"); return execute_ssh_command(scommand, allocateTTY); } @@ -182,21 +206,23 @@ bool server_env_manager::run_remote_template_command(const std::string &service_ bool server_env_manager::run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command, std::vector args, std::string &output, bool silent) const { sCommand scommand = construct_standard_command_run_cmd(service_name, command, args, silent); + if (scommand.get_command_to_run().empty()) + return false; bool allocateTTY = (command=="ssh"); return execute_ssh_command_and_capture_output(scommand, output, allocateTTY); } 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; + if (command.get_command_to_run().empty()) + return false; + int ret = system(command.construct_safecmd().c_str()); + return EXITSTATUSCHECK(ret); } bool server_env_manager::execute_local_command_interactive(const sCommand &command) { + if (command.get_command_to_run().empty()) + return false; std::string full_command = command.construct_rawcmd(); // Get the command string pid_t pid = fork(); @@ -217,23 +243,20 @@ 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) { + if (command.get_command_to_run().empty()) + return false; std::string full_cmd = command.construct_safecmd() + " 2>&1"; FILE *pipe = popen(full_cmd.c_str(), "r"); if (!pipe) { @@ -243,17 +266,15 @@ 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); } std::string sCommand::construct_safecmd() const { + if (mCmd.empty()) + return ""; std::string to_encode; for (const auto& env_var : mVars) { @@ -272,6 +293,9 @@ std::string sCommand::construct_safecmd() const std::string sCommand::construct_rawcmd() const { + if (mCmd.empty()) + return ""; + std::string rawcmd; if (!mDir.empty()) rawcmd = "cd " + quote(mDir) + " && "; @@ -288,6 +312,8 @@ std::string sCommand::construct_rawcmd() const std::string makesafecmd(const std::string &command) { + if (command.empty()) + return ""; std::string encoded = base64_encode(dequote(trim(command))); std::string commandstr = "echo " + encoded + " | base64 -d | bash"; return commandstr; diff --git a/src/server_env_manager.hpp b/src/server_env_manager.hpp index 8e1ac20..d25d58d 100644 --- a/src/server_env_manager.hpp +++ b/src/server_env_manager.hpp @@ -18,7 +18,7 @@ class sCommand { mDir(directory_to_run_in), mCmd(command_to_run), mVars(env_vars) {} sCommand(std::string command_to_run) : mDir(""), mCmd(command_to_run), mVars({}) {} - + sCommand() : mDir(""), mCmd(""), mVars({}) {} std::string get_directory_to_run_in() const { return mDir; } std::string get_command_to_run() const { return mCmd; } @@ -29,6 +29,8 @@ class sCommand { std::string construct_safecmd() const; std::string construct_rawcmd() const; + bool empty() const { return mCmd.empty(); } + private: std::string mDir; std::string mCmd; @@ -64,6 +66,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/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/service_runner.cpp b/src/service_runner.cpp index 16b687b..08a802e 100644 --- a/src/service_runner.cpp +++ b/src/service_runner.cpp @@ -31,6 +31,9 @@ service_runner::service_runner(const std::string& server_name, const std::string return; mServiceInfo = get_service_info(server_name, service_name); + if (mServiceInfo.service_name.empty()) + return; + mService = mServiceInfo.service_name; mValid = !mServiceInfo.local_template_path.empty(); @@ -144,7 +147,7 @@ bool service_runner::uninstall() { return true; } -bool service_runner::nuke() +bool service_runner::nuke(bool silent) { maketitle("Nuking " + mService + " (" + mServiceInfo.template_name + ") on " + mServer); @@ -156,10 +159,22 @@ bool service_runner::nuke() else { // try uninstalling. - mServerEnv.run_remote_template_command(mService, "uninstall", {}); - mServerEnv.run_remote_template_command(mService, "nuke", {}); + if (gTemplateManager().template_command_exists(mServiceInfo.template_name, "uninstall")) + if (!mServerEnv.run_remote_template_command(mService, "uninstall", {})) + std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl; - std::string rm_cmd = "rm -rf " + quote(remotepath::service(mServer, mService)); + // try nuke script. + if (gTemplateManager().template_command_exists(mServiceInfo.template_name, "nuke")) + if (!mServerEnv.run_remote_template_command(mService, "nuke", {})) + std::cerr << "Warning: Nuke script failed, but continuing with directory removal" << std::endl; + + // try rm -rf. + std::string remotedir = remotepath::service(mServer, mService); + std::string remoteparentdir = get_parent(remotedir); + std::string remotechilddir = get_child(remotedir); + + std::string cmd = quote("rm -rf /parent/"+remotechilddir); + std::string rm_cmd = "docker run --rm -v "+quote(remoteparentdir)+":/parent debian bash -c "+cmd; if (!mServerEnv.execute_ssh_command(rm_cmd)) { std::cerr << "Failed to remove service directory" << std::endl; return false; @@ -167,24 +182,37 @@ bool service_runner::nuke() } std::cout << "Service " << mService << " successfully nuked from " << mServer << std::endl; - std::cout << "Now deleteing local files..." << std::endl; - std::string local_service_path = localpath::service(mServer,mService); - if (local_service_path.empty() || !fs::exists(local_service_path)) { - std::cerr << "Error: Service directory not found: " << local_service_path << std::endl; - } - else - { - std::string rm_cmd = "rm -rf " + quote(local_service_path); - if (!mServerEnv.execute_local_command(rm_cmd)) { - std::cerr << "Failed to remove service directory" << std::endl; - return false; - } - } - std::cout << "Service " << mService << " successfully nuked from " << mServer << std::endl; + if (!silent) { + std::cout << "There's nothing left on the remote server." << std::endl; + std::cout << "You can remove the local files with:" << std::endl; + std::cout << " rm -rf " << localpath::service(mServer,mService) << std::endl; + } return true; } +bool service_runner::fullnuke() +{ + if (!nuke(true)) + { + std::cerr << "Warning: Nuke script failed, aborting fullnuke!" << std::endl; + return false; + } + + std::string local_service_path = mServiceInfo.local_service_path; + if (local_service_path.empty() || !fs::exists(local_service_path)) { + std::cerr << "Error: Service directory not found: " << local_service_path << std::endl; + return false; + } + + std::string rm_cmd = "rm -rf " + quote(local_service_path); + if (!mServerEnv.execute_local_command(rm_cmd)) { + std::cerr << "Failed to remove service directory" << std::endl; + return false; + } + + return true; +} // ------------------------------------------------------------------------------------------------ @@ -207,6 +235,15 @@ bool service_runner::run_command(const std::string& command, std::vector> confirm; - if (confirm != 'y') { - std::cout << "Restore cancelled." << std::endl; - return false; - } // run the restore script std::cout << "OK, here goes..." << std::endl; @@ -556,8 +591,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 +689,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:"< 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 d67adbe..88b66d7 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 @@ -82,7 +78,8 @@ class service_runner { bool restore(std::string backup_file, bool silent=false); // nuke the service - bool nuke(); + bool nuke(bool silent=false); // nukes all data for this service on the remote server + bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles // launch an interactive ssh session on a server or service // replaces the current dropshell process with the ssh process @@ -91,6 +88,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; @@ -102,11 +109,15 @@ class service_runner { public: }; - -// 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); + class cRemoteTempFolder { + public: + cRemoteTempFolder(const server_env_manager & server_env); // create a temp folder on the remote server + ~cRemoteTempFolder(); // delete the temp folder on the remote server + std::string path() const; // get the path to the temp folder on the remote server + private: + std::string mPath; + const server_env_manager & mServerEnv; + }; } // namespace dropshell diff --git a/src/services.cpp b/src/services.cpp index 52d72ad..3285c1f 100644 --- a/src/services.cpp +++ b/src/services.cpp @@ -14,6 +14,13 @@ namespace fs = std::filesystem; namespace dropshell { + bool SIvalid(const LocalServiceInfo& service_info) { + return !service_info.service_name.empty() && + !service_info.template_name.empty() && + !service_info.local_service_path.empty() && + !service_info.local_template_path.empty(); + } + std::vector get_server_services_info(const std::string& server_name) { std::vector services; @@ -64,7 +71,8 @@ LocalServiceInfo get_service_info(const std::string &server_name, const std::str // now set the template name and path. std::map variables; - get_all_service_env_vars(server_name, service_name, variables); + if (!get_all_service_env_vars(server_name, service_name, variables)) + return LocalServiceInfo(); // confirm TEMPLATE is defined. auto it = variables.find("TEMPLATE"); @@ -207,10 +215,16 @@ bool create_service(const std::string &server_name, const std::string &template_ -void get_all_service_env_vars(const std::string &server_name, const std::string &service_name, std::map & all_env_vars) +bool get_all_service_env_vars(const std::string &server_name, const std::string &service_name, std::map & all_env_vars) { all_env_vars.clear(); + if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name))) + { + std::cerr << "Error: Service not found: " << service_name << std::endl; + return false; + } + // add in some handy variables. all_env_vars["CONFIG_PATH"] = remotepath::service_config(server_name,service_name); all_env_vars["SERVER"] = server_name; @@ -247,13 +261,22 @@ void get_all_service_env_vars(const std::string &server_name, const std::string std::cerr << "The TEMPLATE variable is required to determine the template name." << std::endl; std::cerr << "Please check the service.env file and the .template_info.env file in:" << std::endl; std::cerr << " " << localpath::service(server_name, service_name) << std::endl << std::endl; - return; + return false; } template_info tinfo = gTemplateManager().get_template_info(it->second); - if (tinfo.is_set()) - load_env_file(tinfo.local_template_path()/"_default.env"); - else + if (!tinfo.is_set()) { std::cerr << "Error: Template '" << it->second << "' not found" << std::endl; + return false; + } + + std::string default_env_file = tinfo.local_template_path()/"_default.env"; + if (!fs::exists(default_env_file)) { + std::cerr << "Error: Template default env file '" << default_env_file << "' not found" << std::endl; + return false; + } + + load_env_file(default_env_file); + return true; } } // namespace dropshell diff --git a/src/services.hpp b/src/services.hpp index c6b8bb6..447cc12 100644 --- a/src/services.hpp +++ b/src/services.hpp @@ -15,13 +15,15 @@ namespace dropshell { std::string local_template_path; }; + bool SIvalid(const LocalServiceInfo& service_info); + std::vector get_server_services_info(const std::string& server_name); LocalServiceInfo get_service_info(const std::string& server_name, const std::string& service_name); std::set get_used_commands(const std::string& server_name, const std::string& service_name); // get all env vars for a given service - void get_all_service_env_vars(const std::string& server_name, const std::string& service_name, std::map & all_env_vars); + bool get_all_service_env_vars(const std::string& server_name, const std::string& service_name, std::map & all_env_vars); // list all backups for a given service (across all servers) std::set list_backups(const std::string& server_name, const std::string& service_name); 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/src/utils/directories.cpp b/src/utils/directories.cpp index 32c9e61..3e43780 100644 --- a/src/utils/directories.cpp +++ b/src/utils/directories.cpp @@ -136,6 +136,12 @@ namespace remotepath { return (dsp.empty() ? "" : (dsp + "/backups")); } + std::string temp_files(const std::string &server_name) + { + std::string dsp = DROPSHELL_DIR(server_name); + return (dsp.empty() ? "" : (dsp + "/temp_files")); + } + std::string service_env(const std::string &server_name, const std::string &service_name) { std::string service_path = service_config(server_name, service_name); @@ -147,11 +153,18 @@ namespace remotepath { // ------------------------------------------------------------------------------------------ // Utility functions -std::string get_parent(const std::string &path) +std::string get_parent(const std::filesystem::path path) { if (path.empty()) return std::string(); - return fs::path(path).parent_path().string(); + return path.parent_path().string(); +} + +std::string get_child(const std::filesystem::path path) +{ + if (path.empty()) + return std::string(); + return path.filename().string(); } } // namespace dropshell diff --git a/src/utils/directories.hpp b/src/utils/directories.hpp index 54022e2..860f52c 100644 --- a/src/utils/directories.hpp +++ b/src/utils/directories.hpp @@ -2,6 +2,7 @@ #define DIRECTORIES_HPP #include +#include namespace dropshell { @@ -62,6 +63,7 @@ namespace dropshell { // remote paths // DROPSHELL_DIR // |-- backups + // |-- temp_files // |-- services // |-- service name // |-- config @@ -83,12 +85,14 @@ namespace dropshell { std::string service_config(const std::string &server_name, const std::string &service_name); std::string service_template(const std::string &server_name, const std::string &service_name); std::string backups(const std::string &server_name); + std::string temp_files(const std::string &server_name); } // namespace remotepath //------------------------------------------------------------------------------------------------ // utility functions - std::string get_parent(const std::string &path); - + std::string get_parent(const std::filesystem::path path); + std::string get_child(const std::filesystem::path path); } // namespace dropshell + #endif // DIRECTORIES_HPP diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index d892ae9..c8a32e7 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -6,6 +6,8 @@ #include #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/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 c181ccb..c338757 100644 --- a/templates/dropshell-agent/shared/_autocommands.sh +++ b/templates/dropshell-agent/shared/_autocommands.sh @@ -1,27 +1,99 @@ #!/bin/bash +MYID=$(id -u) +MYGRP=$(id -g) -_autocommandrun() { +_autocommandrun_volume() { command="$1" - key="$2" - value="$3" + volume_name="$2" - # only passed through if command is backup or restore. - backup_file="$4" - temp_path="$5" - - case "$key" in - volume) - echo "Volume: $value" + case "$command" in + create) + echo "Creating volume ${volume_name}" + docker volume create ${volume_name} ;; - path) - echo "Path: $value" + nuke) + echo "Nuking volume ${volume_name}" + docker volume rm ${volume_name} ;; - file) - echo "File: $value" + backup) + local backup_folder="$3" + echo "Backing up volume ${volume_name}" + 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" ;; - *) - _die "Unknown key $key passed to auto${command}. We only support volume, path and file." + restore) + 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 ${backup_folder}:/backup debian bash -c "tar -xzvf /backup/backup.tgz -C /volume --strip-components=1" + ;; + esac +} + +_autocommandrun_path() { + command="$1" + path="$2" + + case "$command" in + create) + echo "Creating path ${path}" + mkdir -p ${path} + ;; + nuke) + echo "Nuking path ${path}" + PATHPARENT=$(dirname ${path}) + PATHCHILD=$(basename ${path}) + if [ -d "${PATHPARENT}/${PATHCHILD}" ]; then + docker run --rm -v ${PATHPARENT}:/volume debian bash -c "rm -rf /volume/${PATHCHILD}" || echo "Failed to nuke path ${path}" + else + echo "Path ${path} does not exist - nothing to nuke" + fi + ;; + backup) + local backup_folder="$3" + echo "Backing up path ${path}" + if [ -d "${path}" ]; then + docker run --rm -v ${path}:/path -v ${backup_folder}:/backup debian bash -c "tar -czvf /backup/backup.tgz -C /path . && chown -R $MYID:$MYGRP /backup" + else + echo "Path ${path} does not exist - nothing to backup" + fi + ;; + restore) + local backup_folder="$3" + echo "Restoring path ${path}" + tar -xzvf ${backup_folder}/backup.tgz -C ${path} --strip-components=1 + ;; + esac +} + +_autocommandrun_file() { + command="$1" + value="$2" + + case "$command" in + create) + ;; + nuke) + rm -f ${value} + ;; + backup) + local backup_folder="$3" + echo "Backing up file ${value}" + + FILEPARENT=$(dirname ${value}) + FILENAME=$(basename ${value}) + if [ -f "${FILEPARENT}/${FILENAME}" ]; then + docker run --rm-v ${FILEPARENT}:/volume -v ${backup_folder}:/backup debian bash -c "cp /volume/${FILENAME} /backup/${FILENAME} && chown -R $MYID:$MYGRP /backup" + else + echo "File ${value} does not exist - nothing to backup" + fi + ;; + restore) + local backup_folder="$3" + echo "Restoring file ${value}" + local filename=$(basename ${value}) + cp ${backup_folder}/${filename} ${value} ;; esac } @@ -35,19 +107,22 @@ _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="$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[@]} - 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 @@ -57,30 +132,81 @@ _autocommandparse() { local key="${pair%%=*}" local value="${pair#*=}" - + + # 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 - _autocommandrun "$command" "$key" "$value" "$backup_file" "$temp_path" + case "$key" in + volume) + _autocommandrun_volume "$command" "$value" "$targetpath" + ;; + path) + _autocommandrun_path "$command" "$value" "$targetpath" + ;; + file) + _autocommandrun_file "$command" "$value" "$targetpath" + ;; + *) + _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 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" . } 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/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/squashkiwi/nuke.sh b/templates/squashkiwi/nuke.sh new file mode 100644 index 0000000..d3e4cc0 --- /dev/null +++ b/templates/squashkiwi/nuke.sh @@ -0,0 +1,8 @@ +#!/bin/bash +source "${AGENT_PATH}/_common.sh" +_check_required_env_vars "LOCAL_DATA_FOLDER" + +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" 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." diff --git a/templates/test_template.sh b/templates/test_template.sh index afd38d1..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