From ce5a64a4c71263f3b0ed7fd71e16254bd0d59060 Mon Sep 17 00:00:00 2001
From: Your Name <j@842.be>
Date: Mon, 5 May 2025 22:53:41 +1200
Subject: [PATCH] .

---
 src/main.cpp                                  | 74 ++++++++++---------
 src/service_runner.cpp                        | 37 ++++++----
 src/service_runner.hpp                        |  3 +-
 .../dropshell-agent/shared/_autocommands.sh   | 25 +++++--
 4 files changed, 84 insertions(+), 55 deletions(-)

diff --git a/src/main.cpp b/src/main.cpp
index 5c1711e..03c53f3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -40,14 +40,17 @@ bool 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)"<<std::endl;
     std::cout << "  create-template TEMPLATE" << std::endl;
@@ -116,18 +119,17 @@ int main(int argc, char* argv[]) {
         if (gConfig().is_config_set()) 
             gTemplateManager().load_sources();
 
-        if (argc < 2) {
-            print_help();
-            return 0;
-        }
+        if (argc < 2) 
+            return print_help() ? 0 : 1;
+
         std::string cmd = argv[1];
-        std::vector<std::string> argvec;
-        for (int i=0; i<argc; i++) 
-            argvec.push_back(argv[i]);
 
-
-        if (cmd == "autocomplete") 
+        if (cmd == "autocomplete") {
+            std::vector<std::string> argvec;
+            for (int i=0; i<argc; i++) 
+                argvec.push_back(argv[i]);
             return autocomplete(argvec) ? 0 : 1;
+        }
 
         if (cmd == "help" || cmd == "-h" || cmd == "--help" || cmd== "h" || cmd=="halp") 
             return print_help() ? 0 : 1;
@@ -216,30 +218,32 @@ int main(int argc, char* argv[]) {
         std::set<std::string> commands;
         get_all_used_commands(commands);
         commands.merge(std::set<std::string>{"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;
-                }
 
-                for (const auto& service_info : server_and_services.servicelist) {
-                    service_runner runner(server_and_services.server_name, service_info.service_name);
-                    if (!runner.isValid()) {
-                        std::cerr << "Error: Failed to initialize service" << std::endl;
-                        return 1;
-                    }
-                    std::vector<std::string> additional_args;
-                    for (int i=4; i<argc; i++)
-                        additional_args.push_back(argv[i]);
-                    if (!runner.run_command(command, additional_args)) {
-                        std::cerr << command +" failed." << std::endl;
-                        return 1;
-                    }
-                }
-                return 0;
+        if (commands.count(cmd)) {
+            std::set<std::string> 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) {
+                service_runner runner(server_and_services.server_name, service_info.service_name);
+                if (!runner.isValid()) 
+                    return die("Error: Failed to initialize service");
+                
+                std::vector<std::string> additional_args;
+                for (int i=4; i<argc; i++)
+                    additional_args.push_back(argv[i]);
+                if (!runner.run_command(cmd, additional_args)) 
+                    return die(cmd+" failed.");
             }
+
+            // success!
+            return 0;
         }
 
         // Unknown command
diff --git a/src/service_runner.cpp b/src/service_runner.cpp
index f9f5bba..e8b322f 100644
--- a/src/service_runner.cpp
+++ b/src/service_runner.cpp
@@ -167,24 +167,33 @@ 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;
+
+    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 (!mServerEnv.is_valid()) return false; // should never hit this.
+
+    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;
+        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;
+}
 
 
 // ------------------------------------------------------------------------------------------------
diff --git a/src/service_runner.hpp b/src/service_runner.hpp
index 6923936..412253f 100644
--- a/src/service_runner.hpp
+++ b/src/service_runner.hpp
@@ -78,7 +78,8 @@ class service_runner {
         bool restore(std::string backup_file, bool silent=false);
 
         // nuke the service
-        bool nuke();
+        bool nuke(); // 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
diff --git a/templates/dropshell-agent/shared/_autocommands.sh b/templates/dropshell-agent/shared/_autocommands.sh
index ff19ba3..85dccdf 100644
--- a/templates/dropshell-agent/shared/_autocommands.sh
+++ b/templates/dropshell-agent/shared/_autocommands.sh
@@ -42,12 +42,22 @@ _autocommandrun_path() {
             ;;
         nuke)
             echo "Nuking path ${path}"
-            rm -rf ${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}"
+            else
+                echo "Path ${path} does not exist - nothing to nuke"
+            fi
             ;;
         backup)
             local backup_folder="$3"
             echo "Backing up path ${path}"
-            tar -czvf ${backup_folder}/backup.tgz -C ${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"
@@ -70,9 +80,14 @@ _autocommandrun_file() {
         backup)
             local backup_folder="$3"
             echo "Backing up file ${value}"
-            # get filename from path
-            local filename=$(basename ${value})
-            cp ${value} ${backup_folder}/${filename}
+
+            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"