273 lines
10 KiB
C++
273 lines
10 KiB
C++
#include "version.hpp"
|
|
#include "config.hpp"
|
|
#include "service_runner.hpp"
|
|
#include "services.hpp"
|
|
#include "servers.hpp"
|
|
#include "utils/directories.hpp"
|
|
#include "templates.hpp"
|
|
#include "utils/utils.hpp"
|
|
#include "autocomplete.hpp"
|
|
#include "utils/hash.hpp"
|
|
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <iomanip>
|
|
#include <chrono>
|
|
#include <libassert/assert.hpp>
|
|
|
|
namespace dropshell {
|
|
|
|
extern const std::string VERSION;
|
|
extern const std::string RELEASE_DATE;
|
|
extern const std::string AUTHOR;
|
|
extern const std::string LICENSE;
|
|
|
|
bool print_help() {
|
|
std::cout << std::endl;
|
|
maketitle("DropShell version " + VERSION);
|
|
std::cout << std::endl;
|
|
std::cout << "A tool for managing server configurations" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "dropshell ..." << std::endl;
|
|
std::cout << " help Show this help message" << std::endl;
|
|
std::cout << " edit Edit the configuration of dropshell" << std::endl;
|
|
|
|
if (gConfig().is_config_set()) {
|
|
std::cout << " server NAME Show details for specific server" << std::endl;
|
|
std::cout << " templates List all available templates" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Service commands: (if no service is specified, all services for the server are affected)" << std::endl;
|
|
std::cout << " list [SERVER] [SERVICE] List status/details of all servers/server/service." << std::endl;
|
|
std::cout << " edit [SERVER] [SERVICE] Edit the configuration of dropshell/server/service." << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << " install SERVER [SERVICE] Install/reinstall/update service(s). Safe/non-destructive." << std::endl;
|
|
std::cout << " uninstall SERVER [SERVICE] Uninstalls the service on the remote server. Leaves data intact." << std::endl;
|
|
std::cout << " nuke SERVER SERVICE Nuke the service on the remote server, deleting all remote data." << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << " COMMAND SERVER [SERVICE] Run a command on service(s), e.g." << std::endl;
|
|
std::cout << " backup, restore, start, stop, logs" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << " ssh SERVER SERVICE Launch an interactive shell on a server or service" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Creation commands: (apply to the first local config directory)"<<std::endl;
|
|
std::cout << " create-template TEMPLATE" << std::endl;
|
|
std::cout << " create-server SERVER" << std::endl;
|
|
std::cout << " create-service SERVER TEMPLATE SERVICE" << std::endl;
|
|
}
|
|
else {
|
|
std::cout << " edit Edit the configuration of dropshell" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Other commands available once initialised." << std::endl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int die(const std::string & msg) {
|
|
std::cerr << msg << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
struct ServerAndServices {
|
|
std::string server_name;
|
|
std::vector<LocalServiceInfo> servicelist;
|
|
};
|
|
|
|
bool getCLIServices(const std::string & arg2, const std::string & arg3,
|
|
ServerAndServices & server_and_services)
|
|
{
|
|
if (arg2.empty()) return false;
|
|
server_and_services.server_name = arg2;
|
|
|
|
if (arg3.empty()) {
|
|
server_and_services.servicelist = get_server_services_info(arg2);
|
|
} else {
|
|
server_and_services.servicelist.push_back(get_service_info(arg2, arg3));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string safearg(int argc, char *argv[], int index)
|
|
{
|
|
if (index >= argc) return "";
|
|
return argv[index];
|
|
}
|
|
|
|
void printversion() {
|
|
maketitle("DropShell version " + VERSION);
|
|
std::cout << "Release date: " << RELEASE_DATE << std::endl;
|
|
std::cout << "Author: " << AUTHOR << std::endl;
|
|
std::cout << "License: " << LICENSE << std::endl;
|
|
}
|
|
|
|
#define HAPPYEXIT(CMD, RUNCMD) {if (safearg(argc,argv,1) == CMD) {RUNCMD; return 0;}}
|
|
#define BOOLEXIT(CMD, RUNCMD) {if (safearg(argc,argv,1) == CMD) {return (RUNCMD) ? 0 : 1;}}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
HAPPYEXIT("hash", hash_demo_raw(safearg(argc,argv,2)))
|
|
HAPPYEXIT("makesafecmd", std::cout<<makesafecmd(safearg(argc,argv,2))<<std::endl)
|
|
HAPPYEXIT("version", printversion())
|
|
BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2)))
|
|
ASSERT(safearg(argc,argv,1) != "assert", "Hello! Here is an assert.");
|
|
|
|
try {
|
|
// silently attempt to load the config file and templates.
|
|
gConfig().load_config();
|
|
if (gConfig().is_config_set())
|
|
gTemplateManager().load_sources();
|
|
|
|
if (argc < 2)
|
|
return print_help() ? 0 : 1;
|
|
|
|
std::string cmd = argv[1];
|
|
|
|
if (cmd == "autocomplete") {
|
|
std::vector<std::string> argvec;
|
|
for (int i=0; i<argc; i++)
|
|
argvec.push_back(argv[i]);
|
|
return autocomplete(argvec) ? 0 : 1;
|
|
}
|
|
|
|
if (cmd == "help" || cmd == "-h" || cmd == "--help" || cmd== "h" || cmd=="halp")
|
|
return print_help() ? 0 : 1;
|
|
|
|
if (cmd == "edit" && argc < 3) {
|
|
if (!gConfig().is_config_set())
|
|
gConfig().save_config(false);
|
|
|
|
std::string config_file = localfile::dropshell_json();
|
|
if (!service_runner::edit_file(config_file) || !std::filesystem::exists(config_file))
|
|
return die("Error: Failed to edit config file.");
|
|
|
|
gConfig().load_config();
|
|
if (!gConfig().is_config_set())
|
|
return die("Error: Failed to load and parse edited config file.");
|
|
|
|
gConfig().save_config(true);
|
|
std::cout << "Successfully edited config file at " << config_file << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
// from here we require the config file to be loaded.
|
|
if (!gConfig().is_config_set())
|
|
return die("Please run 'dropshell edit' to set up the dropshell configuration.");
|
|
|
|
const std::vector<std::string> & server_definition_paths = gConfig().get_local_server_definition_paths();
|
|
if (server_definition_paths.size()>1) { // only show if there are multiple.
|
|
std::cout << "Server definition paths: ";
|
|
for (auto & dir : server_definition_paths)
|
|
std::cout << "["<< dir << "] ";
|
|
std::cout << std::endl;
|
|
}
|
|
if (gTemplateManager().is_loaded() && gTemplateManager().get_source_count() > 0)
|
|
gTemplateManager().print_sources();
|
|
|
|
if (cmd == "server" || cmd == "servers" || cmd == "list" || cmd == "view")
|
|
switch (argc)
|
|
{
|
|
case 2:
|
|
list_servers();
|
|
return 0;
|
|
case 3:
|
|
show_server_details(argv[2]);
|
|
return 0;
|
|
case 4:
|
|
cmd="logs";
|
|
break;
|
|
default:
|
|
return die("dropshell server: too many arguments");
|
|
}
|
|
|
|
if (cmd == "templates") {
|
|
gTemplateManager().list_templates();
|
|
return 0;
|
|
}
|
|
|
|
if (cmd == "create-template") {
|
|
if (argc < 3) return die("Error: create-template requires a template name");
|
|
return (gTemplateManager().create_template(argv[2])) ? 0 : 1;
|
|
}
|
|
|
|
if (cmd == "create-server") {
|
|
if (argc < 3) return die("Error: create-server requires a server name");
|
|
return (create_server(argv[2])) ? 0 : 1;
|
|
}
|
|
|
|
if (cmd == "create-service") {
|
|
if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service");
|
|
return (create_service(argv[2], argv[3], argv[4])) ? 0 : 1;
|
|
}
|
|
|
|
if (cmd == "ssh" && argc < 4) {
|
|
if (argc < 3) return die("Error: ssh requires a server name and optionally service name");
|
|
service_runner::interactive_ssh(argv[2], "bash");
|
|
return 0;
|
|
}
|
|
|
|
if (cmd == "edit" && argc < 4) {
|
|
ASSERT(argc>=3, "Error: logic error!");
|
|
service_runner::edit_server(safearg(argc,argv,2));
|
|
return 0;
|
|
}
|
|
|
|
// handle running a command.
|
|
std::set<std::string> commands;
|
|
get_all_used_commands(commands);
|
|
commands.merge(std::set<std::string>{"ssh","edit","_allservicesstatus","fullnuke"}); // handled by service_runner, but not in template_shell_commands.
|
|
|
|
if (commands.count(cmd)) {
|
|
std::set<std::string> safe_commands = {"nuke", "fullnuke"};
|
|
if (safe_commands.count(cmd) && argc < 4)
|
|
return die("Error: "+cmd+" requires a server name and service name. For safety, can't run on all services.");
|
|
|
|
// get all the services to run the command on.
|
|
ServerAndServices server_and_services;
|
|
if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services))
|
|
return die("Error: "+cmd+" command requires server name and optionally service name");
|
|
|
|
// run the command on each service.
|
|
for (const auto& service_info : server_and_services.servicelist) {
|
|
if (!SIvalid(service_info))
|
|
std::cerr<<"Error: Unable to get service information."<<std::endl;
|
|
else {
|
|
service_runner runner(server_and_services.server_name, service_info.service_name);
|
|
if (!runner.isValid())
|
|
return die("Error: Failed to initialize service");
|
|
|
|
std::vector<std::string> additional_args;
|
|
for (int i=4; i<argc; i++)
|
|
additional_args.push_back(argv[i]);
|
|
if (!runner.run_command(cmd, additional_args))
|
|
return die(cmd+" failed on service "+service_info.service_name);
|
|
}
|
|
}
|
|
|
|
// success!
|
|
return 0;
|
|
}
|
|
|
|
// Unknown command
|
|
std::cerr << "Error: Unknown command '" << cmd << "'" << std::endl;
|
|
std::cerr << "Valid commands: ";
|
|
for (const auto& command : commands) {
|
|
if (!command.empty() && command[0]!='_')
|
|
std::cerr << command << " ";
|
|
}
|
|
std::cerr << std::endl;
|
|
return 1;
|
|
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "Error: " << e.what() << std::endl;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
} // namespace dropshell
|
|
|
|
int main(int argc, char* argv[]) {
|
|
return dropshell::main(argc, argv);
|
|
} |