diff --git a/source/src/commands/install.cpp b/source/src/commands/install.cpp index d63b7a3..49aacb0 100644 --- a/source/src/commands/install.cpp +++ b/source/src/commands/install.cpp @@ -9,6 +9,7 @@ #include "autogen/_agent-remote.hpp" #include "services.hpp" #include "utils/output.hpp" +#include "utils/env_validator.hpp" #include #include @@ -150,6 +151,41 @@ namespace dropshell return false; } + // Validate service.env matches template service.env + { + std::filesystem::path template_service_env = tinfo.local_template_path() / "config" / "service.env"; + std::string service_env_file = localfile::service_env(server, service); + + std::vector missing_vars; + std::vector extra_vars; + + if (!validate_and_fix_service_env(template_service_env.string(), service_env_file, missing_vars, extra_vars)) + { + error << "Service environment file validation failed for " << service << std::endl; + + if (!missing_vars.empty()) { + error << "Missing variables (added to service.env): "; + for (size_t i = 0; i < missing_vars.size(); ++i) { + error << missing_vars[i]; + if (i < missing_vars.size() - 1) error << ", "; + } + error << std::endl; + } + + if (!extra_vars.empty()) { + error << "Extra variables (commented out in service.env): "; + for (size_t i = 0; i < extra_vars.size(); ++i) { + error << extra_vars[i]; + if (i < extra_vars.size() - 1) error << ", "; + } + error << std::endl; + } + + error << "Please run 'dropshell edit " << server << " " << service << "' to review and fix the service.env file" << std::endl; + return false; + } + } + // Run install script { info << "Running " << service_info.template_name << " install script on " << server << "..." << std::endl; diff --git a/source/src/utils/env_validator.cpp b/source/src/utils/env_validator.cpp new file mode 100644 index 0000000..1c98337 --- /dev/null +++ b/source/src/utils/env_validator.cpp @@ -0,0 +1,145 @@ +#include "env_validator.hpp" +#include "envmanager.hpp" +#include "ordered_env.hpp" +#include "utils.hpp" +#include "output.hpp" + +#include +#include +#include +#include +#include + +namespace dropshell { + +bool validate_and_fix_service_env( + const std::string& template_service_env_path, + const std::string& service_env_path, + std::vector& missing_vars, + std::vector& extra_vars +) { + missing_vars.clear(); + extra_vars.clear(); + + // Load template service.env + envmanager template_env(template_service_env_path); + if (!template_env.load()) { + error << "Failed to load template service.env from: " << template_service_env_path << std::endl; + return false; + } + + ordered_env_vars template_vars; + template_env.get_all_variables(template_vars); + + // Create a set of template variable names for quick lookup + std::set template_var_names; + for (const auto& [key, value] : template_vars) { + template_var_names.insert(key); + } + + // Load service service.env + envmanager service_env(service_env_path); + if (!service_env.load()) { + error << "Failed to load service service.env from: " << service_env_path << std::endl; + return false; + } + + ordered_env_vars service_vars; + service_env.get_all_variables(service_vars); + + // Create a set of service variable names for quick lookup + std::set service_var_names; + for (const auto& [key, value] : service_vars) { + service_var_names.insert(key); + } + + // Find missing variables (in template but not in service) + for (const auto& template_var_name : template_var_names) { + if (service_var_names.find(template_var_name) == service_var_names.end()) { + missing_vars.push_back(template_var_name); + } + } + + // Find extra variables (in service but not in template) + for (const auto& service_var_name : service_var_names) { + if (template_var_names.find(service_var_name) == template_var_names.end()) { + extra_vars.push_back(service_var_name); + } + } + + // If there are no differences, validation passes + if (missing_vars.empty() && extra_vars.empty()) { + return true; + } + + // There are differences - need to fix the service.env file + debug << "Fixing service.env file: " << service_env_path << std::endl; + + // Read the original file line by line + std::ifstream infile(service_env_path); + if (!infile.is_open()) { + error << "Failed to open service.env for reading: " << service_env_path << std::endl; + return false; + } + + std::vector lines; + std::string line; + while (std::getline(infile, line)) { + lines.push_back(line); + } + infile.close(); + + // Process lines and comment out extra variables + std::set extra_vars_set(extra_vars.begin(), extra_vars.end()); + for (auto& line : lines) { + std::string trimmed = trim(line); + + // Skip empty lines and comments + if (trimmed.empty() || trimmed[0] == '#') { + continue; + } + + // Check if this line defines a variable + size_t pos = trimmed.find('='); + if (pos != std::string::npos) { + std::string key = trim(trimmed.substr(0, pos)); + key = dequote(key); + + // If this is an extra variable, comment it out + if (extra_vars_set.find(key) != extra_vars_set.end()) { + // Only comment out if not already commented + if (line[0] != '#') { + line = "# " + line; + } + } + } + } + + // Add missing variables at the end + if (!missing_vars.empty()) { + lines.push_back(""); + lines.push_back("# Variables added from template update:"); + for (const auto& var_name : missing_vars) { + // Get the value from the template + std::string value = get_var(template_vars, var_name); + lines.push_back(var_name + "=" + quote(value)); + } + } + + // Write the modified file back + std::ofstream outfile(service_env_path); + if (!outfile.is_open()) { + error << "Failed to open service.env for writing: " << service_env_path << std::endl; + return false; + } + + for (const auto& l : lines) { + outfile << l << std::endl; + } + outfile.close(); + + // Validation failed because we had to make changes + return false; +} + +} // namespace dropshell diff --git a/source/src/utils/env_validator.hpp b/source/src/utils/env_validator.hpp new file mode 100644 index 0000000..478812e --- /dev/null +++ b/source/src/utils/env_validator.hpp @@ -0,0 +1,27 @@ +#ifndef ENV_VALIDATOR_HPP +#define ENV_VALIDATOR_HPP + +#include +#include + +namespace dropshell { + +// Validates that a service's service.env file matches the template's service.env file +// Returns true if validation passes +// Returns false if there are mismatches (and fixes them by adding missing vars and commenting out extras) +// +// Parameters: +// template_service_env_path: Full path to the template's service.env file +// service_env_path: Full path to the service's service.env file +// missing_vars: Output parameter - list of variable names that were missing and added +// extra_vars: Output parameter - list of variable names that were extra and commented out +bool validate_and_fix_service_env( + const std::string& template_service_env_path, + const std::string& service_env_path, + std::vector& missing_vars, + std::vector& extra_vars +); + +} // namespace dropshell + +#endif // ENV_VALIDATOR_HPP