#include "command_registry.hpp" #include "config.hpp" #include "utils/utils.hpp" #include "utils/directories.hpp" #include "utils/execute.hpp" #include "shared_commands.hpp" #include "servers.hpp" #include "services.hpp" #include "templates.hpp" #include "utils/output.hpp" #include namespace dropshell { int exec_handler(const CommandContext &ctx); static std::vector exec_name_list = {"exec"}; // Static registration struct ExecCommandRegister { ExecCommandRegister() { CommandRegistry::instance().register_command({exec_name_list, exec_handler, shared_commands::std_autocomplete, false, // hidden true, // requires_config true, // requires_install 3, // min_args (server, service, command) -1, // max_args (unlimited for command with args) "exec SERVER SERVICE COMMAND [ARGS...]", "Execute a command on a server within a service environment.", R"( exec SERVER SERVICE COMMAND [ARGS...] Execute a command in the service's environment. This command runs a command on the remote server with the service's environment variables loaded (including those from service.env and the usual variables). The command is executed in the service's template directory context. )"}); } } exec_command_register; int exec_handler(const CommandContext &ctx) { if (ctx.args.size() < 3) { error << "Server name, service name, and command are required" << std::endl; return 1; } std::string server = safearg(ctx.args, 0); std::string service = safearg(ctx.args, 1); std::string command = safearg(ctx.args, 2); // Collect any additional arguments for the command std::vector command_args; for (size_t i = 3; i < ctx.args.size(); ++i) { command_args.push_back(ctx.args[i]); } // Validate server ServerConfig server_env(server); if (!server_env.is_valid()) { error << "Server " << server << " is not valid" << std::endl; return 1; } // Validate service name if (!legal_service_name(service)) { error << "Service name contains illegal characters: " << service << std::endl; return 1; } // Get service info to validate it exists LocalServiceInfo sinfo = get_service_info(server, service); if (!SIvalid(sinfo)) { error << "Service " << service << " is not valid on server " << server << std::endl; return 1; } // Get the user for this service std::string user = server_env.get_user_for_service(service); // Get all service environment variables std::map env_vars; if (!get_all_service_env_vars(server, service, env_vars)) { error << "Failed to get environment variables for service " << service << std::endl; return 1; } // Add HOST_NAME like other commands do env_vars["HOST_NAME"] = server_env.get_SSH_HOST(); // Get the remote service template path for working directory std::string remote_service_template_path = remotepath(server, user).service_template(service); // Build the command string with arguments std::string full_command = command; for (const auto &arg : command_args) { full_command += " " + quote(dequote(trim(arg))); } // Create the command structure with environment variables // Note: execute_ssh_command will automatically use bb64 to encode and execute this safely sCommand scommand(remote_service_template_path, full_command, env_vars); // Execute the command on the remote server info << "Executing command on " << server << "/" << service << ": " << command; if (!command_args.empty()) { rawout << " with args:"; for (const auto &arg : command_args) rawout << " " << arg; } rawout << std::endl; bool success = execute_ssh_command(server_env.get_SSH_INFO(user), scommand, cMode::Interactive); if (!success) { error << "Command execution failed" << std::endl; return 1; } return 0; } } // namespace dropshell