#include "command_registry.hpp" #include "directories.hpp" #include "shared_commands.hpp" #include "templates.hpp" #include #include "utils/utils.hpp" #include "services.hpp" #include namespace dropshell { int create_service_handler(const CommandContext &ctx); void create_service_autocomplete(const CommandContext &ctx); static std::vector create_service_name_list = {"create-service"}; // Static registration struct CreateServiceCommandRegister { CreateServiceCommandRegister() { CommandRegistry::instance().register_command({create_service_name_list, create_service_handler, create_service_autocomplete, false, // hidden true, // requires_config true, // requires_install 3, // min_args (after command) 3, // max_args (after command) "create-service SERVER SERVICE TEMPLATE", "Create a service on a server.", // heredoc R"( Create a service on a server. create-service SERVER SERVICE TEMPLATE create the given service on the given server. )"}); } } create_service_command_register; int create_service_handler(const CommandContext &ctx) { std::string server = safearg(ctx.args, 0); std::string service = safearg(ctx.args, 1); std::string template_name = safearg(ctx.args, 2); return shared_commands::create_service(server, template_name, service) ? 0 : 1; } void create_service_autocomplete(const CommandContext &ctx) { if (ctx.args.size() < 2) shared_commands::std_autocomplete(ctx); else { if (ctx.args.size() == 2) { std::set templates = gTemplateManager().get_template_list(); for (const auto &template_name : templates) rawout << template_name << std::endl; } } } namespace shared_commands { bool print_readme(const template_info &tinfo, std::string server, std::string service) { std::vector variants_to_try = {"README.txt", "readme.txt", "ReadMe.txt", "README", "readme", "README.md", "readme.md"}; std::filesystem::path readme_path = tinfo.local_template_path(); for (const auto &variant : variants_to_try) { if (std::filesystem::exists(readme_path / variant)) { readme_path = readme_path / variant; break; } } if (!std::filesystem::exists(readme_path)) return false; std::map all_env_vars; get_all_service_env_vars(server, service, all_env_vars); all_env_vars["LOCAL_CONFIG_PATH"] = localpath::service(server, service); all_env_vars["LOCAL_TEMPLATE_PATH"] = tinfo.local_template_path().string(); info << std::endl; std::ifstream readme_file(readme_path); std::string line; while (std::getline(readme_file, line)) { rawout << substitute_provided_key_value_pairs(line, all_env_vars) << std::endl; } return true; } bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, std::string user_override/*=""*/) { if (server_name.empty() || template_name.empty() || service_name.empty()) return false; ServerConfig server_info(server_name); if (!server_info.is_valid()) { error << "Server " << server_name << " is not valid" << std::endl; return false; } std::string service_dir = localpath::service(server_name, service_name); if (service_dir.empty()) { error << "Couldn't locate server " << server_name << " in any config directory" << std::endl; info << "Please check the server name is correct and try again" << std::endl; info << "You can list all servers with 'dropshell servers'" << std::endl; info << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl; return false; } if (std::filesystem::exists(service_dir)) { error << "Service already exists: " << service_name << std::endl; debug << "Current service path: " << service_dir << std::endl; return false; } template_info tinfo = gTemplateManager().get_template_info(template_name); if (!tinfo.is_set()) { error << "Template '" << template_name << "' not found" << std::endl; info << "Please check the template name is correct and try again" << std::endl; info << "You can list all templates with 'dropshell templates'" << std::endl; info << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl; return false; } // check template is all good. if (!gTemplateManager().test_template(tinfo.local_template_path())) { error << "Template '" << template_name << "' is not valid" << std::endl; return false; } // create the service directory std::filesystem::create_directory(service_dir); // copy the template config files to the service directory recursive_copy(tinfo.local_template_path() / "config", service_dir); // append TEMPLATE_HASH to the .template_info.env file std::string template_info_env_file = localfile::template_info_env(server_name,service_name); ASSERT(std::filesystem::exists(template_info_env_file), "Template info env file not found: " + template_info_env_file); std::ofstream template_info_env_file_out(template_info_env_file, std::ios::app); // append to the file. template_info_env_file_out << "TEMPLATE_HASH=" << tinfo.hash() << std::endl; template_info_env_file_out.close(); // modify the SSH_USER to be nice. // everything is created, so we can get the service info. LocalServiceInfo service_info = get_service_info(server_name, service_name); std::string sshuser = "root"; if (!user_override.empty()) sshuser = user_override; else if (!service_info.requires_host_root) { // find a non-root user. auto users = server_info.get_users(); auto it = std::find_if(users.begin(), users.end(), [&sshuser](const UserConfig &user) { return user.user != "root"; }); if (it != users.end()) sshuser = it->user; } if (sshuser == "root" && !server_info.hasRootUser()) { error << "Server " << server_name << " does not have a root user, but the service " << service_name << " requires it." << std::endl; return false; } if (sshuser != "root" && service_info.requires_host_root) { error << "The service " << service_name << " requires a root user, but a non-root user was specified." << std::endl; return false; } if (!server_info.hasUser(sshuser)) { error << "User " << sshuser << "is not available on server " << server_name << std::endl; return false; } info << "Setting SSH_USER to " << sshuser << " in the " << filenames::service_env << " file" << std::endl; { // edit the service.env file to set the SSH_USER. std::string source_service_env = tinfo.local_template_path() / "config" / filenames::service_env; ASSERT(std::filesystem::exists(source_service_env), "Template service env file not found: " + source_service_env); std::ifstream template_service_env_file_in(source_service_env); std::ofstream service_env_file_out(localfile::service_env(server_name, service_name)); std::string line; while (std::getline(template_service_env_file_in, line)) { if (line.find("SSH_USER") != std::string::npos) line = "SSH_USER=" + sshuser; service_env_file_out << line << std::endl; } template_service_env_file_in.close(); service_env_file_out.close(); } // check docker. if (service_info.requires_docker) { if (!server_info.hasDocker()) { error << "Server " << server_name << " does not have docker, but the service " << service_name << " requires it." << std::endl; return false; } if (service_info.requires_docker_root) { if (!server_info.hasRootDocker()) { error << "Server " << server_name << " does not have a root docker, but the service " << service_name << " requires it." << std::endl; return false; } } } info << "Service " << service_name << " created successfully" << std::endl; if (!print_readme(tinfo, server_name, service_name)) { info << std::endl; info << "To complete the installation, please:" << std::endl; info << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl; info << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl; } return true; } bool merge_updated_service_template(const std::string &server_name, const std::string &service_name) { LocalServiceInfo service_info = get_service_info(server_name, service_name); ASSERT(SIvalid(service_info), "Service info is not valid for " + service_name + " on " + server_name); template_info tinfo = gTemplateManager().get_template_info(service_info.template_name); ASSERT(tinfo.is_set(), "Failed to load template " + service_info.template_name); // copy across .template_info.env file std::string template_info_env_file = tinfo.local_template_path() / "config" / filenames::template_info_env; std::string target_template_info_env_file = localfile::template_info_env(server_name, service_name); ASSERT(std::filesystem::exists(template_info_env_file), "Template service env file not found: " + template_info_env_file); std::filesystem::remove(target_template_info_env_file); std::filesystem::copy(template_info_env_file, target_template_info_env_file); #pragma message("TODO: merge the template info env file") // update hash in template info env file // append TEMPLATE_HASH to the .template_info.env file ASSERT(std::filesystem::exists(target_template_info_env_file), "Template info env file not found: " + target_template_info_env_file); std::ofstream template_info_env_file_out(target_template_info_env_file, std::ios::app); // append to the file. template_info_env_file_out << "TEMPLATE_HASH=" << tinfo.hash() << std::endl; template_info_env_file_out.close(); return true; } } // namespace shared_commands } // namespace dropshell