diff --git a/source/src/commands/create-service.cpp b/source/src/commands/create-service.cpp index 2ebc8f4..e870ad5 100644 --- a/source/src/commands/create-service.cpp +++ b/source/src/commands/create-service.cpp @@ -7,6 +7,7 @@ #include #include "utils/utils.hpp" #include "utils/service_env_validator.hpp" +#include "utils/service_env_overrides.hpp" #include "services.hpp" #include @@ -230,6 +231,13 @@ namespace dropshell return false; } + // apply overrides from overrides.env (if present in server definition path) + { + auto changes = apply_overrides(server_name, service_name); + for (const auto& c : changes) + debug << "Override applied: " << c.key << "=" << c.new_val << std::endl; + } + // check docker. if (service_info.requires_docker) { diff --git a/source/src/commands/edit.cpp b/source/src/commands/edit.cpp index 0f64b3f..d9e7ee8 100644 --- a/source/src/commands/edit.cpp +++ b/source/src/commands/edit.cpp @@ -8,7 +8,9 @@ #include "templates.hpp" #include +#include #include +#include #include #include #include @@ -32,12 +34,13 @@ struct EditCommandRegister { false, // requires_install 0, // min_args (after command) 2, // max_args (after command) - "edit [SERVER] [SERVICE]", - "Edit dropshell, server or service configuration", + "edit [SERVER] [SERVICE] | edit override", + "Edit dropshell, server, service, or override configuration", // heredoc R"( Edit dropshell, server or service configuration. edit edit the dropshell config. + edit override edit the overrides.env for a server location. edit edit the server config. edit edit the service config. )" @@ -241,12 +244,70 @@ int edit_service_config(const std::string &server, const std::string &service) return 0; } +// ------------------------------------------------------------------------------------------------ +// edit override +// ------------------------------------------------------------------------------------------------ +int edit_override() +{ + auto paths = gConfig().get_local_server_definition_paths(); + if (paths.empty()) { + error << "No server definition paths configured." << std::endl; + return 1; + } + + std::string chosen_path; + if (paths.size() == 1) { + chosen_path = paths[0]; + } else { + // Multiple paths - ask user to choose with single keypress + info << "Multiple server locations found. Choose one:" << std::endl; + for (size_t i = 0; i < paths.size() && i < 9; ++i) + info << " " << (i + 1) << ") " << paths[i] << std::endl; + + info << "Enter choice [1-" << std::min(paths.size(), (size_t)9) << "]: " << std::flush; + + // Read single keypress + struct termios oldt, newt; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + int ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + info << (char)ch << std::endl; + + int idx = ch - '1'; + if (idx < 0 || idx >= (int)paths.size()) { + error << "Invalid choice." << std::endl; + return 1; + } + chosen_path = paths[idx]; + } + + std::string override_file = (std::filesystem::path(chosen_path) / filenames::overrides_env).string(); + + // Create with comment header if it doesn't exist + if (!std::filesystem::exists(override_file)) { + std::ofstream f(override_file); + f << "# Overrides for all services in this server location." << std::endl; + f << "# Variables set here will be forced into every service.env" << std::endl; + f << "# during create-service and install." << std::endl; + f << "#" << std::endl; + f << "# Format is the same as service.env:" << std::endl; + f << "# VARIABLE_NAME=\"value\"" << std::endl; + f << std::endl; + info << "Created new overrides file: " << override_file << std::endl; + } + + return edit_file(override_file, true) ? 0 : 1; +} + // ------------------------------------------------------------------------------------------------ // edit command handler // ------------------------------------------------------------------------------------------------ int edit_handler(const CommandContext& ctx) { // edit dropshell config - if (ctx.args.size() < 1) + if (ctx.args.size() < 1) { if (ctx.command=="create-config") { @@ -258,6 +319,10 @@ int edit_handler(const CommandContext& ctx) { return edit_config(); } + // edit override - check before server/service dispatch + if (safearg(ctx.args, 0) == "override") + return edit_override(); + // edit server config if (ctx.args.size() < 2) { edit_server(safearg(ctx.args,0)); diff --git a/source/src/commands/install.cpp b/source/src/commands/install.cpp index 1716739..32e2431 100644 --- a/source/src/commands/install.cpp +++ b/source/src/commands/install.cpp @@ -10,6 +10,7 @@ #include "services.hpp" #include "utils/output.hpp" #include "utils/service_env_validator.hpp" +#include "utils/service_env_overrides.hpp" #include #include @@ -147,6 +148,13 @@ namespace dropshell } } + // Apply overrides from overrides.env (if present in server definition path) + { + auto changes = apply_overrides(server, service); + for (const auto& c : changes) + warning << "Override: " << c.key << " changed from \"" << c.old_val << "\" to \"" << c.new_val << "\"" << std::endl; + } + // ── Stage new template + config to _staging folder on remote ── std::string staging_path = remote_service_path + "/_staging"; std::string staging_template = staging_path + "/template"; diff --git a/source/src/utils/directories.cpp b/source/src/utils/directories.cpp index fa5324b..c40d337 100644 --- a/source/src/utils/directories.cpp +++ b/source/src/utils/directories.cpp @@ -43,6 +43,14 @@ namespace dropshell return localpath::agent_remote() + "/agent.hash"; } + std::string overrides_env_for_server(const std::string &server_name) + { + std::string serverpath = localpath::server(server_name); + if (serverpath.empty()) + return ""; + return (fs::path(get_parent(serverpath)) / filenames::overrides_env).string(); + } + } // namespace localfile std::string get_local_agent_hash() diff --git a/source/src/utils/directories.hpp b/source/src/utils/directories.hpp index b82974b..cfa4b91 100644 --- a/source/src/utils/directories.hpp +++ b/source/src/utils/directories.hpp @@ -43,6 +43,7 @@ namespace dropshell { static const std::string ds_run = "ds_run.sh"; static const std::string template_paths_json = "template_paths.json"; static const std::string dropshell_templates_list = "dropshell-templates.list"; + static const std::string overrides_env = "overrides.env"; } // namespace filenames. namespace localfile { @@ -51,6 +52,7 @@ namespace dropshell { std::string service_env(const std::string &server_name, const std::string &service_name); std::string bb64(); std::string agent_hash(); // Returns path to agent.hash file + std::string overrides_env_for_server(const std::string &server_name); // overrides.env in server's definition path } // namespace localfile // Get the content of the local agent hash (empty string if not found) diff --git a/source/src/utils/service_env_overrides.cpp b/source/src/utils/service_env_overrides.cpp new file mode 100644 index 0000000..fdc7b42 --- /dev/null +++ b/source/src/utils/service_env_overrides.cpp @@ -0,0 +1,51 @@ +#include "service_env_overrides.hpp" +#include "directories.hpp" +#include "envmanager.hpp" +#include "service_env_validator.hpp" + +#include + +namespace dropshell { + +std::vector apply_overrides( + const std::string& server_name, + const std::string& service_name) +{ + std::vector changes; + + std::string overrides_path = localfile::overrides_env_for_server(server_name); + if (overrides_path.empty() || !std::filesystem::exists(overrides_path)) + return changes; + + std::string service_env_path = localfile::service_env(server_name, service_name); + if (service_env_path.empty() || !std::filesystem::exists(service_env_path)) + return changes; + + // Load overrides + envmanager overrides(overrides_path); + if (!overrides.load()) + return changes; + + ordered_env_vars override_vars; + overrides.get_all_variables(override_vars); + if (override_vars.empty()) + return changes; + + // Load current service env + envmanager service_env(service_env_path); + if (!service_env.load()) + return changes; + + // Apply each override + for (const auto& [key, new_val] : override_vars) { + std::string current_val = service_env.get_variable(key); + if (current_val != new_val) { + set_env_variable(service_env_path, key, new_val); + changes.push_back({key, current_val, new_val}); + } + } + + return changes; +} + +} // namespace dropshell diff --git a/source/src/utils/service_env_overrides.hpp b/source/src/utils/service_env_overrides.hpp new file mode 100644 index 0000000..57d5c47 --- /dev/null +++ b/source/src/utils/service_env_overrides.hpp @@ -0,0 +1,24 @@ +#ifndef SERVICE_ENV_OVERRIDES_HPP +#define SERVICE_ENV_OVERRIDES_HPP + +#include +#include + +namespace dropshell { + +struct OverrideChange { + std::string key; + std::string old_val; + std::string new_val; +}; + +// Apply overrides from overrides.env (in the server's definition path) to a service's service.env. +// Returns a list of changes that were made. +// If no overrides.env exists, returns empty (no-op). +std::vector apply_overrides( + const std::string& server_name, + const std::string& service_name); + +} // namespace dropshell + +#endif // SERVICE_ENV_OVERRIDES_HPP