345 lines
12 KiB
C++
345 lines
12 KiB
C++
#include "command_registry.hpp"
|
|
#include "config.hpp"
|
|
#include "utils/utils.hpp"
|
|
#include "utils/directories.hpp"
|
|
#include "utils/service_env_validator.hpp"
|
|
#include "shared_commands.hpp"
|
|
#include "services.hpp"
|
|
#include "templates.hpp"
|
|
|
|
#include <unistd.h>
|
|
#include <termios.h>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <filesystem>
|
|
#include <libassert/assert.hpp>
|
|
|
|
namespace dropshell {
|
|
|
|
int edit_handler(const CommandContext& ctx);
|
|
|
|
static std::vector<std::string> edit_name_list={"edit","create-config"};
|
|
|
|
// Static registration
|
|
struct EditCommandRegister {
|
|
EditCommandRegister() {
|
|
CommandRegistry::instance().register_command({
|
|
edit_name_list,
|
|
edit_handler,
|
|
shared_commands::std_autocomplete,
|
|
false, // hidden
|
|
false, // requires_config
|
|
false, // requires_install
|
|
0, // min_args (after command)
|
|
2, // max_args (after command)
|
|
"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 <server> edit the server config.
|
|
edit <server> <service> edit the service config.
|
|
)"
|
|
});
|
|
}
|
|
} edit_command_register;
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// edit command implementation
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// utility function to edit a file
|
|
// ------------------------------------------------------------------------------------------------
|
|
bool edit_file(const std::string &file_path, bool has_bb64)
|
|
{
|
|
// make sure parent directory exists.
|
|
std::string parent_dir = get_parent(file_path);
|
|
std::filesystem::create_directories(parent_dir);
|
|
|
|
std::string editor_cmd;
|
|
const char* editor_env = std::getenv("EDITOR");
|
|
|
|
if (editor_env && std::strlen(editor_env) > 0) {
|
|
editor_cmd = std::string(editor_env) + " " + quote(file_path);
|
|
} else if (isatty(STDIN_FILENO)) {
|
|
// Check if stdin is connected to a terminal if EDITOR is not set
|
|
editor_cmd = "nano -w " + quote(file_path);
|
|
} else {
|
|
error << "Standard input is not a terminal and EDITOR environment variable is not set." << std::endl;
|
|
info << "Try setting the EDITOR environment variable (e.g., export EDITOR=nano) or run in an interactive terminal." << std::endl;
|
|
info << "You can manually edit the file at: " << file_path << std::endl;
|
|
return false;
|
|
}
|
|
|
|
info << "Editing file: " << file_path << std::endl;
|
|
|
|
if (has_bb64) {
|
|
return execute_local_command("", editor_cmd, {}, nullptr, cMode::Interactive);
|
|
}
|
|
else {
|
|
// might not have bb64 at this early stage. Direct edit.
|
|
int ret = system(editor_cmd.c_str());
|
|
return EXITSTATUSCHECK(ret);
|
|
}
|
|
}
|
|
|
|
int create_config()
|
|
{
|
|
if (!gConfig().is_config_set())
|
|
{
|
|
bool ok = gConfig().save_config(); // save defaults.
|
|
info << "Default dropshell.json created." << std::endl;
|
|
return (ok ? 0 : 1);
|
|
}
|
|
else
|
|
info << "Existing dropshell.json unchanged." << std::endl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// edit config
|
|
// ------------------------------------------------------------------------------------------------
|
|
int edit_config()
|
|
{
|
|
create_config();
|
|
|
|
std::string config_file = localfile::dropshell_json();
|
|
if (!edit_file(config_file, false) || !std::filesystem::exists(config_file))
|
|
return return_die("Failed to edit config file.");
|
|
|
|
gConfig().load_config();
|
|
if (!gConfig().is_config_set())
|
|
return return_die("Failed to load and parse edited config file!");
|
|
|
|
// Don't save_config after loading - it would rewrite the file the user just edited!
|
|
// The config is already saved by the editor, we just validated it by loading it.
|
|
gConfig().create_aux_directories();
|
|
|
|
info << "Successfully edited config file at " << config_file << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// edit server
|
|
// ------------------------------------------------------------------------------------------------
|
|
int edit_server(const std::string &server_name)
|
|
{
|
|
if (localpath::server(server_name).empty()) {
|
|
error << "Server not found: " << server_name << std::endl;
|
|
return -1;
|
|
}
|
|
|
|
std::string config_file = localfile::server_json(server_name);
|
|
|
|
if (!edit_file(config_file, true)) {
|
|
error << "Failed to edit server config" << std::endl;
|
|
info << "You can manually edit this file at: " << config_file << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
info << "If you have changed DROPSHELL_DIR, you should manually move the files to the new location NOW." << std::endl;
|
|
info << "You can ssh in to the remote server with: dropshell ssh "<<server_name<< std::endl;
|
|
info << "Once moved, reinstall all services with: dropshell install " << server_name << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
void list_directory(std::string dir, std::string msg)
|
|
{
|
|
bool first=true;
|
|
std::vector<std::string> directories;
|
|
for (const auto &file : std::filesystem::directory_iterator(dir))
|
|
{
|
|
if (first)
|
|
{
|
|
if (!msg.empty())
|
|
info << msg << std::endl;
|
|
first=false;
|
|
}
|
|
if (std::filesystem::is_directory(file.path()))
|
|
directories.push_back(file.path());
|
|
else
|
|
info << " " << file.path() << std::endl;
|
|
}
|
|
|
|
for (const auto &dir : directories)
|
|
list_directory(dir, "");
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// edit service config
|
|
// ------------------------------------------------------------------------------------------------
|
|
int edit_service_config(const std::string &server, const std::string &service)
|
|
{
|
|
std::string config_file = localfile::service_env(server, service);
|
|
if (!std::filesystem::exists(config_file))
|
|
{
|
|
error << "Service config file not found: " << config_file << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Validate service.env matches template BEFORE editing (adds any missing variables)
|
|
{
|
|
LocalServiceInfo service_info = get_service_info(server, service);
|
|
if (!SIvalid(service_info))
|
|
{
|
|
error << "Failed to get service info for " << service << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
|
|
if (!tinfo.is_set())
|
|
{
|
|
error << "Template not found: " << service_info.template_name << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
std::filesystem::path template_service_env = tinfo.local_template_path() / "config" / "service.env";
|
|
std::filesystem::path template_info_env = tinfo.local_template_path() / "config" / ".template_info.env";
|
|
|
|
std::vector<std::string> missing_vars;
|
|
std::vector<std::string> extra_vars;
|
|
|
|
if (!validate_and_fix_service_env(template_service_env.string(), config_file, missing_vars, extra_vars, template_info_env.string()))
|
|
{
|
|
info << "Service environment file updated to match template:" << std::endl;
|
|
|
|
if (!missing_vars.empty()) {
|
|
info << " Added missing variables: ";
|
|
for (size_t i = 0; i < missing_vars.size(); ++i) {
|
|
info << missing_vars[i];
|
|
if (i < missing_vars.size() - 1) info << ", ";
|
|
}
|
|
info << std::endl;
|
|
}
|
|
|
|
if (!extra_vars.empty()) {
|
|
info << " Commented out extra variables: ";
|
|
for (size_t i = 0; i < extra_vars.size(); ++i) {
|
|
info << extra_vars[i];
|
|
if (i < extra_vars.size() - 1) info << ", ";
|
|
}
|
|
info << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (edit_file(config_file, true) && std::filesystem::exists(config_file))
|
|
info << "Successfully edited service config file at " << config_file << std::endl;
|
|
|
|
std::string service_dir = localpath::service(server, service);
|
|
list_directory(service_dir, "You may wish to edit the other files in " + service_dir);
|
|
|
|
info << "Then to apply your changes, run:" << std::endl;
|
|
info << " dropshell uninstall " + server + " " + service << std::endl;
|
|
info << " dropshell install " + server + " " + service << std::endl;
|
|
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.command=="create-config")
|
|
{
|
|
int rval = create_config();
|
|
gConfig().create_aux_directories();
|
|
return rval;
|
|
}
|
|
else
|
|
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));
|
|
return 0;
|
|
}
|
|
|
|
// edit service config
|
|
if (ctx.args.size() < 3) {
|
|
edit_service_config(safearg(ctx.args,0), safearg(ctx.args,1));
|
|
return 0;
|
|
}
|
|
|
|
info << "Edit handler called with " << ctx.args.size() << " args\n";
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace dropshell
|