Fairly big refactor...

This commit is contained in:
Your Name 2025-04-23 21:50:04 +12:00
parent 50e340f1c5
commit 9a3dd18525
24 changed files with 853 additions and 660 deletions

View File

@ -1,107 +0,0 @@
#include "dropshell.hpp"
#include "init_user_directory.hpp"
#include "config.hpp"
#include <boost/filesystem.hpp>
#include <iostream>
#include <set>
namespace fs = boost::filesystem;
namespace dropshell {
std::vector<std::string> autocomplete_list_servers() {
std::vector<std::string> servers;
std::string user_dir;
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
return servers;
}
fs::path servers_dir = fs::path(user_dir) / "servers";
if (!fs::exists(servers_dir)) {
std::cerr << "Error: Servers directory not found" << std::endl;
return servers;
}
// List all server directories
for (const auto& entry : fs::directory_iterator(servers_dir)) {
if (fs::is_directory(entry)) {
servers.push_back(entry.path().filename().string());
}
}
return servers;
}
std::vector<std::string> autocomplete_list_services(const std::string& server_name) {
std::vector<std::string> services;
std::string user_dir;
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
return services;
}
fs::path server_dir = fs::path(user_dir) / "servers" / server_name;
if (!fs::exists(server_dir)) {
std::cerr << "Error: Server directory not found" << std::endl;
return services;
}
// Look for .env files in the server directory
for (const auto& entry : fs::directory_iterator(server_dir)) {
if (entry.path().extension() == ".env" && entry.path().filename().string() != "_server.env") {
services.push_back(entry.path().stem().string());
}
}
return services;
}
std::vector<std::string> autocomplete_list_commands() {
std::vector<std::string> commands;
std::set<std::string> unique_commands; // To ensure deduplication
NEED TO CHANGE THIS SO IT ITERATES THROUGH ACTUAL INSTALLED SERVICES, NOT TEMPLATES!
// Helper function to add commands from a directory
auto add_commands_from_dir = [&unique_commands](const std::string& dir_path) {
if (!fs::exists(dir_path)) {
return;
}
// Iterate through all template directories
for (const auto& template_entry : fs::directory_iterator(dir_path)) {
if (!fs::is_directory(template_entry)) {
continue;
}
// Look for shell files in each template directory
for (const auto& file_entry : fs::directory_iterator(template_entry.path())) {
if (fs::is_regular_file(file_entry) &&
file_entry.path().extension() == ".sh" &&
file_entry.path().filename().string()[0] != '_') {
unique_commands.insert(file_entry.path().stem().string());
}
}
}
};
// System templates directory
const std::string system_templates_dir = "/opt/dropshell/templates";
add_commands_from_dir(system_templates_dir);
std::string user_templates_dir;
if (get_user_directory(user_templates_dir)) {
// User templates directory
user_templates_dir += "/usertemplates";
add_commands_from_dir(user_templates_dir);
}
// Convert set to vector for return
commands.assign(unique_commands.begin(), unique_commands.end());
return commands;
}
} // namespace dropshell

View File

@ -1,14 +0,0 @@
#ifndef __AUTOCOMPLETE_H
#define __AUTOCOMPLETE_H
#include <string>
#include <vector>
namespace dropshell {
std::vector<std::string> autocomplete_list_servers();
std::vector<std::string> autocomplete_list_services(const std::string& server_name);
} // namespace dropshell
#endif // __AUTOCOMPLETE_H

View File

@ -1,68 +1,87 @@
#include "dropshell.hpp"
#include "init_user_directory.hpp"
#include "utils/directories.hpp"
#include <iostream>
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "config.hpp"
#include "envmanager.hpp"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace dropshell {
// Default user directory
static bool config_loaded = false;
bool is_config_loaded() {
return config_loaded;
config *get_global_config() {
static config *gConfig = new config();
return gConfig;
}
bool get_config_path(std::string &path)
{
// Try ~/.config/dropshell/dropshell.conf
const char* home = std::getenv("HOME");
if (home) {
fs::path user_path = fs::path(home) / ".config" / "dropshell" / "dropshell.conf";
path = user_path.string();
return fs::exists(path);
}
std::cerr << "Warning: Couldn't determine user directory" << std::endl;
path = "";
return false;
config::config() {
}
config::~config() {
}
bool load_config() {
std::string config_path;
if (!get_config_path(config_path))
bool config::load_config() {
std::string config_path = get_local_dropshell_config_path();
if (config_path.empty())
return false;
try {
pt::ptree tree;
pt::read_ini(config_path, tree);
bool config_okay = false;
// Try to read user directory from config
try {
std::string user_dir;
user_dir = tree.get<std::string>("user.directory");
// Update user directory through the new interface
set_user_directory(user_dir);
config_okay = true;
} catch (const pt::ptree_error&) {
std::cerr << "Warning: User directory not set in config" << std::endl;
envmanager config_env(config_path);
if (!config_env.load())
{
std::cerr << "Warning: Unable to read configuration file: " << config_path << std::endl;
return false;
}
// config loaded okay.
config_loaded = config_okay;
return true;
} catch (const std::exception& e) {
std::cerr << "Warning: Error reading config file: " << e.what() << std::endl;
return true; // Not a critical error
}
local_config_directory = config_env.get_variable_substituted("local.config.directory");
if (local_config_directory.empty())
{
std::cerr << "Warning: User directory not set in config" << std::endl;
return false;
}
return true;
}
void config::save_config()
{
std::string config_path = get_local_dropshell_config_path();
if (config_path.empty())
{
std::cerr << "Warning: Unable to save configuration file, as DropShell has not been initialised."<< std::endl;
std::cerr << "Please run 'dropshell init <path>' to initialise DropShell." << std::endl;
return;
}
envmanager config_env(config_path);
config_env.set_variable("local.config.directory", local_config_directory);
config_env.save();
}
bool config::is_config_set() const
{
return !local_config_directory.empty();
}
bool config::get_local_config_directory(std::string& path) const {
path = local_config_directory;
return !path.empty();
}
void config::init_local_config_directory(const std::string& path) {
// Convert to canonical path
fs::path abs_path = fs::canonical(path);
// The directory must exist
if (!fs::exists(abs_path)) {
throw std::runtime_error("The specifieduser directory does not exist: " + abs_path.string());
}
local_config_directory = abs_path.string();
save_config();
std::cout << "Local config directory initialized to: " << abs_path.string() << std::endl;
}
} // namespace dropshell

View File

@ -4,9 +4,23 @@
namespace dropshell {
// Configuration functions
bool get_config_path(std::string& path);
bool load_config();
bool is_config_loaded();
class config {
public:
config();
~config();
bool load_config();
void save_config();
bool is_config_set() const;
bool get_local_config_directory(std::string& path) const;
void init_local_config_directory(const std::string& path);
private:
std::string local_config_directory;
};
config *get_global_config();
} // namespace dropshell

View File

@ -1,27 +0,0 @@
#include "dropshell.hpp"
#include "templates.hpp"
#include <iostream>
#include <iomanip>
void dropshell::list_templates() {
template_manager tm;
std::vector<template_info> templates;
if (!tm.get_templates(templates)) {
std::cerr << "Error: Failed to get templates" << std::endl;
return;
}
if (templates.empty()) {
std::cout << "No templates found." << std::endl;
return;
}
std::cout << "Available templates:" << std::endl;
std::cout << std::left << std::setw(20) << "Name" << "Path" << std::endl;
std::cout << std::string(60, '-') << std::endl;
for (const auto& t : templates) {
std::cout << std::left << std::setw(20) << t.name << t.path << std::endl;
}
}

View File

@ -1,76 +0,0 @@
#include "init_user_directory.hpp"
#include "config.hpp"
#include <iostream>
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace dropshell {
static std::string user_directory;
bool get_user_directory(std::string& path) {
path = user_directory;
return !path.empty();
}
void set_user_directory(const std::string& path) {
user_directory = path;
}
void init_user_directory(const std::string& path) {
// Convert to canonical path
fs::path abs_path = fs::canonical(path);
// The directory must exist
if (!fs::exists(abs_path)) {
throw std::runtime_error("The user directory does not exist: " + abs_path.string());
}
// create the servers subdirectory if it doesn't exist
fs::path servers_dir = abs_path / "servers";
if (!fs::exists(servers_dir)) {
fs::create_directories(servers_dir);
}
// Update config file
std::string config_path;
if (!get_config_path(config_path)) {
// No config file exists, create one in user's home directory
const char* home = std::getenv("HOME");
if (!home) {
throw std::runtime_error("HOME environment variable not set");
}
fs::path config_dir = fs::path(home) / ".config" / "dropshell";
if (!fs::exists(config_dir)) {
fs::create_directories(config_dir);
}
config_path = (config_dir / "dropshell.conf").string();
}
try {
pt::ptree tree;
// Read existing config if it exists
if (fs::exists(config_path)) {
pt::read_ini(config_path, tree);
}
// Update user directory
tree.put("user.directory", abs_path.string());
// Write back to config file
pt::write_ini(config_path, tree);
// Update in-memory value
user_directory = abs_path.string();
std::cout << "User directory initialized to: " << abs_path.string() << std::endl;
} catch (const std::exception& e) {
throw std::runtime_error("Failed to update config: " + std::string(e.what()));
}
}
} // namespace dropshell

View File

@ -1,11 +0,0 @@
#pragma once
#include <string>
namespace dropshell {
// User directory initialization function
void init_user_directory(const std::string& path);
bool get_user_directory(std::string& path);
void set_user_directory(const std::string& path);
} // namespace dropshell

View File

@ -1,7 +1,9 @@
#include "dropshell.hpp"
#include "server_service.hpp"
#include "autocomplete.hpp"
#include "init_user_directory.hpp"
#include "main.hpp"
#include "config.hpp"
#include "service_runner.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "utils/directories.hpp"
#include "config.hpp"
#include <iostream>
@ -28,28 +30,20 @@ void print_help() {
std::cout << " backup SERVER [SERVICE] Backup service(s)." << std::endl;
std::cout << " COMMAND SERVER [SERVICE] Run a custom command on service(s)." << std::endl;
std::cout << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " dropshell servers" << std::endl;
std::cout << " dropshell servers myserver" << std::endl;
std::cout << " dropshell init /path/to/directory" << std::endl;
std::cout << " dropshell templates" << std::endl;
std::cout << " dropshell install myserver myservice" << std::endl;
std::cout << " dropshell run myserver myservice status" << std::endl;
std::cout << " dropshell backup myserver myservice" << std::endl;
}
} // namespace dropshell
bool parseargs(std::string arg2, std::string arg3, std::string & server_name, std::vector<std::string>& servicelist)
bool parseargs(std::string arg2, std::string arg3, std::string & server_name, std::vector<dropshell::ServiceInfo>& servicelist)
{
if (arg2.empty()) return false;
server_name = arg2;
if (arg3.empty()) {
servicelist = dropshell::get_server_services(server_name);
servicelist = dropshell::get_server_services_info(server_name);
} else {
servicelist.push_back(arg3);
servicelist.push_back(dropshell::get_service_info(server_name, arg3));
}
return true;
@ -63,6 +57,7 @@ std::string safearg(int argc, char *argv[], int index)
int main(int argc, char* argv[]) {
try {
dropshell::config *cfg = dropshell::get_global_config();
// Handle commands
std::string cmd;
@ -77,7 +72,7 @@ int main(int argc, char* argv[]) {
return 1;
}
try {
dropshell::init_user_directory(argv[2]);
cfg->init_local_config_directory(argv[2]);
return 0;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
@ -96,27 +91,27 @@ int main(int argc, char* argv[]) {
}
// silently attempt to load the config file.
dropshell::load_config();
cfg->load_config();
// auto completion stuff.
std::set<std::string> commands;
std::vector<dropshell::ServerInfo> servers = dropshell::get_configured_servers();
for (const auto& server : servers)
{
std::vector<dropshell::ServiceInfo> services = dropshell::get_server_services_info(server.name);
for (const auto& service : services)
commands.merge(dropshell::get_used_commands(server.name, service.service_name));
}
// auto compeltion stuff.
auto commands = dropshell::autocomplete_list_commands();
if (cmd == "autocomplete_list_commands") {
// add in standard commands.
commands.insert(commands.end(), {
"help",
"version",
"init"
commands.merge(std::set<std::string>{
"help","version","init"
});
if (dropshell::is_config_loaded()) { // these only work if the config is loaded.
commands.insert(commands.end(), {
"servers",
"templates",
"install",
"backup"
if (cfg->is_config_set())
commands.merge(std::set<std::string>{
"servers","templates","install","backup"
});
}
for (const auto& command : commands) {
std::cout << command << std::endl;
}
@ -124,11 +119,11 @@ int main(int argc, char* argv[]) {
}
if (cmd == "autocomplete_list_servers") {
if (dropshell::is_config_loaded())
if (cfg->is_config_set())
{
auto servers = dropshell::autocomplete_list_servers();
auto servers = dropshell::get_configured_servers();
for (const auto& server : servers)
std::cout << server << std::endl;
std::cout << server.name << std::endl;
}
return 0;
}
@ -138,17 +133,17 @@ int main(int argc, char* argv[]) {
std::cerr << "Error: autocomplete_list_services requires a server name" << std::endl;
return 1;
}
if (dropshell::is_config_loaded()) {
auto services = dropshell::autocomplete_list_services(argv[2]);
if (cfg->is_config_set()) {
auto services = dropshell::get_server_services_info(argv[2]);
for (const auto& service : services)
std::cout << service << std::endl;
std::cout << service.service_name << std::endl;
}
return 0;
}
// ------------------------------------------------------------
// from here we require the config file to be loaded.
if (!dropshell::is_config_loaded()) {
if (!cfg->is_config_set()) {
std::cerr << "Error: Failed to load configuration." << std::endl << "Please run 'dropshell init <path>' to initialise the user directory and create a configuration file." << std::endl;
return 1;
}
@ -177,14 +172,14 @@ int main(int argc, char* argv[]) {
if (cmd == "install") {
std::string server_name;
std::vector<std::string> servicelist;
std::vector<dropshell::ServiceInfo> servicelist;
if (!parseargs(safearg(argc, argv, 2), safearg(argc, argv, 3), server_name, servicelist)) {
std::cerr << "Error: install command requires server name and optionally service name" << std::endl;
return 1;
}
for (const auto& service_name : servicelist) {
dropshell::server_service service;
if (!service.init(server_name, service_name)) {
for (const auto& service_info : servicelist) {
dropshell::service_runner service;
if (!service.init(server_name, service_info.service_name)) {
std::cerr << "Error: Failed to initialize service" << std::endl;
return 1;
}
@ -199,15 +194,15 @@ int main(int argc, char* argv[]) {
if (cmd == "backup") {
std::string server_name;
std::vector<std::string> servicelist;
std::vector<dropshell::ServiceInfo> servicelist;
if (!parseargs(safearg(argc, argv, 2), safearg(argc, argv, 3), server_name, servicelist)) {
std::cerr << "Error: backup command requires server name and optionally service name" << std::endl;
return 1;
}
for (const auto& service_name : servicelist) {
dropshell::server_service service;
if (!service.init(server_name, service_name)) {
for (const auto& service_info : servicelist) {
dropshell::service_runner service;
if (!service.init(server_name, service_info.service_name)) {
std::cerr << "Error: Failed to initialize service" << std::endl;
return 1;
}
@ -224,15 +219,15 @@ int main(int argc, char* argv[]) {
for (const auto& command : commands) {
if (cmd == command) {
std::string server_name;
std::vector<std::string> servicelist;
std::vector<dropshell::ServiceInfo> servicelist;
if (!parseargs(safearg(argc, argv, 2), safearg(argc, argv, 3), server_name, servicelist)) {
std::cerr << "Error: " << command << " command requires server name and optionally service name" << std::endl;
return 1;
}
for (const auto& service_name : servicelist) {
dropshell::server_service service;
if (!service.init(server_name, service_name)) {
for (const auto& service_info : servicelist) {
dropshell::service_runner service;
if (!service.init(server_name, service_info.service_name)) {
std::cerr << "Error: Failed to initialize service" << std::endl;
return 1;
}

View File

@ -13,13 +13,6 @@ const std::string RELEASE_DATE = "2025-04-21";
const std::string AUTHOR = "j842";
const std::string LICENSE = "MIT";
// Server information structure
struct ServerInfo {
std::string name;
std::string ssh_host;
std::string ssh_user;
std::string ssh_port;
};
// Command handlers
void print_help(const boost::program_options::options_description& desc);
@ -30,10 +23,4 @@ void list_templates();
void show_server_details(const std::string& server_name);
void interactive_mode();
// Utility functions
std::vector<ServerInfo> get_configured_servers();
std::vector<std::string> autocomplete_list_servers();
std::vector<std::string> autocomplete_list_services(const std::string& server_name);
std::vector<std::string> autocomplete_list_commands();
} // namespace dropshell

View File

@ -1,121 +1,56 @@
#include "server_env.hpp"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "utils/envmanager.hpp"
#include "utils/directories.hpp"
#include <boost/filesystem.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
namespace dropshell {
// Helper function to trim whitespace from both ends of a string
static std::string trim(const std::string& str) {
const std::string whitespace = " \t";
const auto strBegin = str.find_first_not_of(whitespace);
if (strBegin == std::string::npos) {
return ""; // empty string
}
const auto strEnd = str.find_last_not_of(whitespace);
const auto strRange = strEnd - strBegin + 1;
return str.substr(strBegin, strRange);
}
// Helper function to print the contents of a file to screen
static void print_file(const std::string& path) {
std::cout << "Contents of " << path << ":" << std::endl;
std::ifstream file(path);
std::string line;
while (std::getline(file, line)) {
std::cout << " " << line << std::endl;
}
}
bool server_env::is_valid() {
bool server_env::is_valid() const {
return mValid;
}
server_env::server_env(const std::string& path) : mValid(false) {
// Construct the full path to _server.env
boost::filesystem::path env_path = boost::filesystem::path(path) / "_server.env";
server_env::server_env(const std::string& server_name) : mValid(false) {
// Construct the full path to server.env
std::string env_path = get_local_server_env_path(server_name);
// Check if file exists
if (!boost::filesystem::exists(env_path)) {
throw std::runtime_error("Server environment file not found: " + env_path.string());
throw std::runtime_error("Server environment file not found: " + env_path);
}
try {
// Read the environment file
std::ifstream file(env_path.string());
std::string line;
// Use envmanager to handle the environment file
m_env_manager = std::unique_ptr<envmanager>(new envmanager(env_path));
m_env_manager->load();
while (std::getline(file, line)) {
// Skip empty lines and comments
if (line.empty() || line[0] == '#') {
continue;
}
// Find the position of the equals sign
size_t pos = line.find('=');
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
// Trim whitespace using the helper function
key = trim(key);
value = trim(value);
// Handle ${USER} replacement
size_t user_pos;
while ((user_pos = value.find("${USER}")) != std::string::npos) {
const char* user = std::getenv("USER");
if (user) {
value.replace(user_pos, 7, user);
}
}
variables[key] = value;
}
}
// Get all variables
m_env_manager->get_all_variables_substituted(variables);
// Verify required variables exist
for (const auto& var : {"SSH_HOST", "SSH_USER", "SSH_PORT", "DROPSHELL_DIR"}) {
if (variables.find(var) == variables.end()) {
// print the contents of the _server.env file to screen:
print_file(env_path.string());
// print variables identified in the file:
// Print the variables identified in the file
std::cout << "Variables identified in the file:" << std::endl;
for (const auto& var : variables) {
std::cout << " " << var.first << std::endl;
for (const auto& v : variables) {
std::cout << " " << v.first << std::endl;
}
throw std::runtime_error("Missing required variable: " + std::string(var));
}
}
mValid = true;
} catch (const boost::property_tree::ini_parser_error& e) {
} catch (const std::exception& e) {
throw std::runtime_error("Failed to parse server environment file: " + std::string(e.what()));
}
}
std::string server_env::get_variable(const std::string& name) {
auto it = variables.find(name);
if (it == variables.end()) {
std::string server_env::get_variable(const std::string& name) const {
if (!m_env_manager) {
return "";
}
std::string value = it->second;
// Replace ${USER} with actual username
const char* username = std::getenv("USER");
if (username) {
std::string user_var = "${USER}";
size_t pos = value.find(user_var);
if (pos != std::string::npos) {
value.replace(pos, user_var.length(), username);
}
}
return value;
return m_env_manager->get_variable_substituted(name);
}
const std::map<std::string, std::string> &server_env::get_variables() const
@ -123,19 +58,19 @@ const std::map<std::string, std::string> &server_env::get_variables() const
return variables;
}
std::string server_env::get_SSH_HOST() {
std::string server_env::get_SSH_HOST() const {
return get_variable("SSH_HOST");
}
std::string server_env::get_SSH_USER() {
std::string server_env::get_SSH_USER() const {
return get_variable("SSH_USER");
}
std::string server_env::get_SSH_PORT() {
std::string server_env::get_SSH_PORT() const {
return get_variable("SSH_PORT");
}
std::string server_env::get_DROPSHELL_DIR() {
std::string server_env::get_DROPSHELL_DIR() const {
return get_variable("DROPSHELL_DIR");
}

View File

@ -1,17 +1,17 @@
// server_env.hpp
//
// read the _server.env file and provide a class to access the variables
//
// read the server.env file and provide a class to access the variables
#ifndef __SERVER_ENV_HPP
#define __SERVER_ENV_HPP
#include <string>
#include <map>
#include <memory>
#include "utils/envmanager.hpp"
namespace dropshell {
// reads path / _server.env and provides a class to access the variables.
// reads path / server.env and provides a class to access the variables.
// each env file is required to have the following variables:
// SSH_HOST
// SSH_USER
@ -20,21 +20,22 @@ namespace dropshell {
// ${USER} -> the username of the user running dropshell
class server_env {
public:
server_env(const std::string& path);
std::string get_variable(const std::string& name);
server_env(const std::string& server_name);
std::string get_variable(const std::string& name) const;
const std::map<std::string, std::string>& get_variables() const;
std::string get_SSH_HOST();
std::string get_SSH_USER();
std::string get_SSH_PORT();
std::string get_SSH_HOST() const;
std::string get_SSH_USER() const;
std::string get_SSH_PORT() const;
std::string get_DROPSHELL_DIR();
std::string get_DROPSHELL_DIR() const;
bool is_valid();
bool is_valid() const;
private:
std::map<std::string, std::string> variables;
bool mValid;
std::unique_ptr<envmanager> m_env_manager;
};
} // namespace dropshell

View File

@ -1,9 +1,11 @@
#include "init_user_directory.hpp"
#include "dropshell.hpp"
#include "servers.hpp"
#include "server_env.hpp"
#include "server_service.hpp"
#include "service_runner.hpp"
#include "tableprint.hpp"
#include "interactive/interactive.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp"
#include "services.hpp"
#include <iostream>
#include <fstream>
#include <iomanip>
@ -16,32 +18,29 @@ namespace dropshell {
std::vector<ServerInfo> get_configured_servers() {
std::vector<ServerInfo> servers;
std::string user_dir;
if (!is_config_loaded()) {
std::cerr << "Error: Config not loaded" << std::endl;
return servers;
}
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
return servers;
}
fs::path servers_dir = fs::path(user_dir) / "servers";
if (!fs::exists(servers_dir)) {
std::string servers_dir = get_local_config_servers_path();
if (servers_dir.empty()) {
std::cerr << "Error: Servers directory not found" << std::endl;
return servers;
}
if (!fs::exists(servers_dir)) {
std::cerr << "Error: Servers directory not found:" << servers_dir << std::endl;
return servers;
}
for (const auto& entry : fs::directory_iterator(servers_dir)) {
if (fs::is_directory(entry)) {
fs::path env_file = entry.path();
server_env env(env_file.string());
std::string server_name = entry.path().filename().string();
server_env env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << env_file.string() << std::endl;
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
continue;
}
servers.push_back({
entry.path().filename().string(),
server_name,
env.get_SSH_HOST(),
env.get_SSH_USER(),
env.get_SSH_PORT()
@ -91,10 +90,10 @@ void list_servers() {
for (const auto& server : servers) {
std::vector<int> ports_used;
std::string serviceticks = "";
std::vector<std::string> services = get_server_services(server.name);
std::vector<ServiceInfo> services = get_server_services_info(server.name);
for (const auto& service : services) {
server_service ss;
if (ss.init(server.name, service))
service_runner ss;
if (ss.init(server.name, service.service_name))
{
if (ss.is_healthy())
serviceticks += ":tick: ";
@ -120,21 +119,12 @@ void list_servers() {
}
void show_server_details(const std::string& server_name) {
std::string user_dir;
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
server_env env(server_name);
if (!env.is_valid()) {
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
return;
}
fs::path server_dir = fs::path(user_dir) / "servers" / server_name;
if (!fs::exists(server_dir)) {
std::cerr << "Error: Server '" << server_name << "' not found" << std::endl;
return;
}
server_env env(server_dir.string());
//---------------------
// Check if server is reachable via SSH
std::string ssh_address = env.get_SSH_HOST();
@ -176,12 +166,12 @@ void show_server_details(const std::string& server_name) {
tableprint tp("Services: " + server_name, false);
tp.add_row({"Status", "Service", "Ports"});
std::vector<std::string> services = get_server_services(server_name);
std::vector<ServiceInfo> services = get_server_services_info(server_name);
for (const auto& service : services) {
bool healthy = false;
std::vector<int> ports;
server_service ss;
if (ss.init(server_name, service))
service_runner ss;
if (ss.init(server_name, service.service_name))
{
if (ss.is_healthy())
healthy=true;
@ -196,7 +186,7 @@ void show_server_details(const std::string& server_name) {
ports_str += std::to_string(port);
first = false;
}
tp.add_row({healthy ? ":tick:" : ":cross:", service, ports_str});
tp.add_row({healthy ? ":tick:" : ":cross:", service.service_name, ports_str});
} // end of for (const auto& service : services)
tp.print();
} // end of list services

22
src/servers.hpp Normal file
View File

@ -0,0 +1,22 @@
#ifndef SERVERS_HPP
#define SERVERS_HPP
#include <string>
#include <vector>
namespace dropshell {
// Server information structure
struct ServerInfo {
std::string name;
std::string ssh_host;
std::string ssh_user;
std::string ssh_port;
};
std::vector<ServerInfo> get_configured_servers();
} // namespace dropshell
#endif // SERVERS_HPP

View File

@ -1,9 +1,10 @@
#include "config.hpp"
#include "init_user_directory.hpp"
#include "server_service.hpp"
#include "service_runner.hpp"
#include "server_env.hpp"
#include "templates.hpp"
#include "config.hpp"
#include "services.hpp"
#include "utils/directories.hpp"
#include <boost/filesystem.hpp>
#include <iostream>
#include <fstream>
@ -16,57 +17,13 @@ namespace fs = boost::filesystem;
namespace dropshell {
std::vector<std::string> get_server_services(const std::string& server_name) {
std::vector<std::string> services;
std::string user_dir;
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
return services;
}
fs::path server_dir = fs::path(user_dir) / "servers" / server_name;
if (!fs::exists(server_dir)) {
std::cerr << "Error: Server directory not found" << std::endl;
return services;
}
// Look for .env files in the server directory
for (const auto& entry : fs::directory_iterator(server_dir)) {
if (entry.path().extension() == ".env" && entry.path().filename().string() != "_server.env") {
services.push_back(entry.path().stem().string());
}
}
return services;
}
server_service::server_service() : m_server_name(""), m_service_name(""), m_server_env(nullptr) {}
service_runner::service_runner() : m_server_name(""), m_server_env(nullptr) {}
bool server_service::init(const std::string& server_name, const std::string& service_name) {
std::string user_dir;
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
return false;
}
// Check if server exists
fs::path server_dir = fs::path(user_dir) / "servers" / server_name;
if (!fs::exists(server_dir)) {
std::cerr << "Error: Server '" << server_name << "' not found" << std::endl;
return false;
}
// Check if service env file exists
fs::path service_env = server_dir / (service_name + ".env");
if (!fs::exists(service_env)) {
std::cerr << "Error: Service environment file not found: " << service_env.string() << std::endl;
return false;
}
bool service_runner::init(const std::string& server_name, const std::string& service_name) {
// Initialize server environment
try {
m_server_env = std::make_unique<server_env>(server_dir.string());
m_server_env = std::make_unique<server_env>(server_name);
if (!m_server_env->is_valid()) {
std::cerr << "Error: Invalid server environment" << std::endl;
return false;
@ -75,55 +32,49 @@ bool server_service::init(const std::string& server_name, const std::string& ser
std::cerr << "Error: Failed to initialize server environment: " << e.what() << std::endl;
return false;
}
mRemote_service_path = get_remote_service_path(m_server_name, m_service_info.service_name);
mRemote_service_config_path = get_remote_service_config_path(m_server_name, m_service_info.service_name);
mRemote_service_template_path = get_remote_service_template_path(m_server_name, m_service_info.service_name);
mRemote_service_env_file = get_remote_service_env_file(m_server_name, m_service_info.service_name);
m_server_name = server_name;
m_service_name = service_name;
return true;
m_service_info = get_service_info(server_name, service_name);
return !m_service_info.path.empty();
}
// Helper method implementations
std::string server_service::construct_ssh_cmd() const {
std::string service_runner::construct_ssh_cmd() const {
std::stringstream ssh_cmd;
ssh_cmd << "ssh -p " << m_server_env->get_SSH_PORT() << " "
<< m_server_env->get_SSH_USER() << "@" << m_server_env->get_SSH_HOST() << " ";
return ssh_cmd.str();
}
std::string server_service::get_service_dir() const {
return m_server_env->get_DROPSHELL_DIR() + "/" + m_service_name;
}
std::string server_service::get_env_path() const {
return get_service_dir() + "/" + m_service_name + ".env";
}
std::string server_service::get_script_dir() const {
return get_service_dir() + "/template";
}
bool server_service::check_service_dir_exists(const std::string& ssh_cmd) const {
std::string check_dir_cmd = ssh_cmd + "'test -d " + get_service_dir() + "'";
bool service_runner::check_remote_dir_exists(const std::string& ssh_cmd, const std::string& dir_path) const {
std::string check_dir_cmd = ssh_cmd + "'test -d " + dir_path + "'";
if (system(check_dir_cmd.c_str()) != 0) {
std::cerr << "Error: Service directory not found on server - has it been installed?" << std::endl;
std::cerr << "Error: Directory not found on remote server:" << dir_path << std::endl;
return false;
}
return true;
}
bool server_service::check_remote_file_exists(const std::string& ssh_cmd, const std::string& file_path) const {
bool service_runner::check_remote_file_exists(const std::string& ssh_cmd, const std::string& file_path) const {
std::string check_cmd = ssh_cmd + "'test -f " + file_path + "'";
if (system(check_cmd.c_str()) != 0) {
std::cerr << "Error: File not found: " << file_path << std::endl;
std::cerr << "Error: File not found on remote server: " << file_path << std::endl;
return false;
}
return true;
}
bool server_service::execute_ssh_command(const std::string& command, const std::string& error_msg) const {
bool service_runner::execute_ssh_command(const std::string& command, const std::string& error_msg) const {
std::string full_cmd = construct_ssh_cmd() + command;
return execute_local_command(full_cmd, error_msg);
}
bool server_service::execute_local_command(const std::string& command, const std::string& error_msg) const {
bool service_runner::execute_local_command(const std::string& command, const std::string& error_msg) const {
bool okay = (system(command.c_str()) == 0);
if (!okay && !error_msg.empty())
@ -131,14 +82,14 @@ bool server_service::execute_local_command(const std::string& command, const std
return okay;
}
void server_service::maketitle(const std::string& title) const {
void service_runner::maketitle(const std::string& title) const {
std::cout << std::string(title.length() + 4, '-') << std::endl;
std::cout << "| " << title << " |" << std::endl;
std::cout << std::string(title.length() + 4, '-') << std::endl;
}
bool server_service::install() {
maketitle("Installing " + m_service_name + " on " + m_server_name);
bool service_runner::install() {
maketitle("Installing " + m_service_info.service_name + " (" + m_service_info.template_name + ") on " + m_server_name);
if (!m_server_env) {
std::cerr << "Error: Server service not initialized" << std::endl;
return false;
@ -146,16 +97,14 @@ bool server_service::install() {
// Check if template exists
template_manager tm;
template_info info;
if (!tm.get_template_info(m_service_name, info)) {
std::cerr << "Error: Template '" << m_service_name << "' not found" << std::endl;
template_info tinfo;
if (!tm.get_template_info(m_service_info.template_name, tinfo)) {
std::cerr << "Error: Template '" << m_service_info.template_name << "' not found" << std::endl;
return false;
}
std::string script_dir = get_script_dir();
// Create service directory
std::string mkdir_cmd = "'mkdir -p " + script_dir + "'";
std::string mkdir_cmd = "'mkdir -p " + mRemote_service_path + "'";
if (!execute_ssh_command(mkdir_cmd, "Failed to create service directory")) {
return false;
}
@ -167,52 +116,55 @@ bool server_service::install() {
}
// Copy template files
std::cout << "Copying template files from " << info.path << " to " << script_dir << "/" << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " +
info.path + "/ " +
m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" +
script_dir + "/";
execute_local_command(rsync_cmd,"Failed to copy template files");
// Copy service env file
std::string user_dir;
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
return false;
}
fs::path service_env = fs::path(user_dir) / "servers" / m_server_name / (m_service_name + ".env");
std::string scp_cmd = "scp -P " + m_server_env->get_SSH_PORT() + " " +
service_env.string() + " " +
m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" +
get_env_path();
if (!execute_ssh_command(scp_cmd, "Failed to copy service environment file")) {
return false;
{
std::cout << "Copying template files from " << tinfo.path << " to " << mRemote_service_template_path << "/" << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " +
tinfo.path + "/ " +
m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" +
mRemote_service_template_path + "/";
execute_local_command(rsync_cmd,"Failed to copy template files");
}
// Copy service files (including service.env)
{
std::string local_service_path = get_local_service_path(m_server_name, m_service_info.service_name);
if (local_service_path.empty() || !fs::exists(local_service_path)) {
std::cerr << "Error: Service directory not found: " << local_service_path << std::endl;
return false;
}
std::cout << "Copying service files from " << local_service_path << " to " << mRemote_service_config_path << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + m_server_env->get_SSH_PORT() + "' " +
local_service_path + "/ " +
m_server_env->get_SSH_USER() + "@" + m_server_env->get_SSH_HOST() + ":" +
mRemote_service_config_path + "/";
execute_local_command(rsync_cmd,"Failed to copy service files");
}
// Run install script
std::string install_cmd = "'cd " + script_dir +
" && /bin/bash _install.sh " + get_env_path() + "'";
bool ok= execute_ssh_command(install_cmd, "Failed to run install script");
if (!ok)
return false;
{
std::string install_cmd = "'cd " + mRemote_service_template_path +
" && /bin/bash _install.sh " + mRemote_service_env_file + "'";
bool ok= execute_ssh_command(install_cmd, "Failed to run install script");
if (!ok)
return false;
}
// print health tick
std::cout << "Health: " << healthtick() << std::endl;
return true;
}
bool server_service::run_command(const std::string& command) {
bool service_runner::run_command(const std::string& command) {
if (!m_server_env) {
std::cerr << "Error: Server service not initialized" << std::endl;
return false;
}
std::string ssh_cmd = construct_ssh_cmd();
std::string script_dir = get_script_dir();
std::string script_path = script_dir + "/" + command + ".sh";
std::string script_path = mRemote_service_template_path + "/" + command + ".sh";
// Check if service directory exists
if (!check_service_dir_exists(ssh_cmd)) {
if (!check_remote_dir_exists(ssh_cmd, mRemote_service_path)) {
return false;
}
@ -222,18 +174,18 @@ bool server_service::run_command(const std::string& command) {
}
// Check if env file exists
if (!check_remote_file_exists(ssh_cmd, get_env_path())) {
if (!check_remote_file_exists(ssh_cmd, mRemote_service_env_file)) {
return false;
}
// Run the command
std::string run_cmd = "'cd " + script_dir +
" && /bin/bash " + script_path + " " + get_env_path() + "'";
std::string run_cmd = "'cd " + mRemote_service_template_path +
" && /bin/bash " + script_path + " " + mRemote_service_env_file + "'";
return execute_ssh_command(run_cmd, "Command returned error code: " + script_path);
}
bool server_service::backup() {
maketitle("Backing up " + m_service_name + " on " + m_server_name);
bool service_runner::backup() {
maketitle("Backing up " + m_service_info.service_name + " (" + m_service_info.template_name + ") on " + m_server_name);
if (!m_server_env) {
std::cerr << "Error: Server service not initialized" << std::endl;
@ -241,11 +193,10 @@ bool server_service::backup() {
}
std::string ssh_cmd = construct_ssh_cmd();
std::string script_dir = get_script_dir();
std::string script_path = script_dir + "/_backup.sh";
std::string script_path = mRemote_service_template_path + "/_backup.sh";
// Check if basic installed stuff is in place.
if (!check_service_dir_exists(ssh_cmd) || !check_remote_file_exists(ssh_cmd, script_path) || !check_remote_file_exists(ssh_cmd, get_env_path()))
if (!check_remote_dir_exists(ssh_cmd, mRemote_service_path) || !check_remote_file_exists(ssh_cmd, script_path) || !check_remote_file_exists(ssh_cmd, mRemote_service_env_file))
return false;
// Create backups directory on server if it doesn't exist
@ -256,12 +207,7 @@ bool server_service::backup() {
}
// Create backups directory locally if it doesn't exist
std::string user_dir;
if (!get_user_directory(user_dir)) {
std::cerr << "Error: User directory not set" << std::endl;
return false;
}
fs::path local_backups_dir = fs::path(user_dir) / "backups";
std::string local_backups_dir = get_local_config_backups_path();
if (!fs::exists(local_backups_dir)) {
fs::create_directories(local_backups_dir);
}
@ -273,13 +219,13 @@ bool server_service::backup() {
datetime << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
// Construct backup filename
std::string backup_filename = m_server_name + "-" + m_service_name + "-" + datetime.str() + ".tgz";
std::string backup_filename = m_server_name + "-" + m_service_info.service_name + "-" + datetime.str() + ".tgz";
std::string server_backup_path = server_backups_dir + "/" + backup_filename;
std::string local_backup_path = (local_backups_dir / backup_filename).string();
std::string local_backup_path = (fs::path(local_backups_dir) / backup_filename).string();
// Run backup script
std::string backup_cmd = "'cd " + script_dir +
" && /bin/bash \""+script_path+"\" " + get_env_path() + " " + server_backup_path + "'";
std::string backup_cmd = "'cd " + mRemote_service_template_path +
" && /bin/bash \""+script_path+"\" " + mRemote_service_env_file + " " + server_backup_path + "'";
if (!execute_ssh_command(backup_cmd, "Backup script failed")) {
return false;
}
@ -296,22 +242,18 @@ bool server_service::backup() {
return true;
}
bool server_service::is_healthy()
bool service_runner::is_healthy()
{
if (!m_server_env) {
std::cerr << "Error: Server service not initialized" << std::endl;
return false;
}
std::string service_dir = m_server_env->get_DROPSHELL_DIR() + "/" + m_service_name;
std::string script_dir = service_dir + "/template";
std::string env_path = service_dir + "/" + m_service_name + ".env";
// Run status script, does not display output.
return execute_ssh_command("'cd " + script_dir + " && /bin/bash _status.sh " + env_path + " > /dev/null 2>&1'","");
return execute_ssh_command("'cd " + mRemote_service_template_path + " && /bin/bash _status.sh " + mRemote_service_env_file + " > /dev/null 2>&1'","");
}
std::string server_service::healthtick()
std::string service_runner::healthtick()
{
std::string green_tick = "\033[32m✓\033[0m";
std::string red_cross = "\033[31m✗\033[0m";
@ -322,7 +264,7 @@ std::string server_service::healthtick()
return red_cross;
}
std::vector<int> server_service::get_ports()
std::vector<int> service_runner::get_ports()
{
std::vector<int> ports;
if (!m_server_env) {
@ -331,22 +273,16 @@ std::vector<int> server_service::get_ports()
}
std::string ssh_cmd = construct_ssh_cmd();
std::string script_dir = get_script_dir();
std::string script_path = script_dir + "/_ports.sh";
// Check if service directory exists
if (!check_service_dir_exists(ssh_cmd)) {
return ports;
}
std::string script_path = mRemote_service_template_path + "/_ports.sh";
// Check if ports script exists
if (!check_remote_file_exists(ssh_cmd, script_path)) {
return ports;
}
// Run the ports script and capture output
std::string run_cmd = "'cd " + script_dir +
" && /bin/bash " + script_path + " " + get_env_path() + "'";
std::string run_cmd = "'cd " + mRemote_service_template_path +
" && /bin/bash " + script_path + " " + mRemote_service_env_file + "'";
// Create a temporary file to store the output
std::string temp_file = "/tmp/dropshell_ports_" + std::to_string(getpid());

View File

@ -7,17 +7,16 @@
#include <vector>
#include <memory>
#include "server_env.hpp"
#include "services.hpp"
namespace dropshell {
std::vector<std::string> get_server_services(const std::string& server_name);
class server_service {
class service_runner {
public:
server_service();
service_runner();
bool init(const std::string& server_name, const std::string& service_name);
// install the service over ssh, using the credentials from _server.env (via server_env.hpp), by:
// install the service over ssh, using the credentials from server.env (via server_env.hpp), by:
// 1. check if the server_name exists, and the service_name refers to a valid template
// 2. check if service_name is valid for the server_name
// 3. create the service directory on the server at {DROPSHELL_DIR}/{service_name}
@ -26,7 +25,7 @@ class server_service {
// 5. running the install.sh script on the server, passing the {service_name}.env file as an argument
bool install();
// run a command over ssh, using the credentials from _server.env (via server_env.hpp)
// run a command over ssh, using the credentials from server.env (via server_env.hpp)
// first check that the command corresponds to a valid .sh file in the service directory
// then run the command, passing the {service_name}.env file as an argument
// do a lot of checks, such as:
@ -37,7 +36,7 @@ class server_service {
// checking that the {service_name}.env file exists in the service directory.
bool run_command(const std::string& command);
// backup the service over ssh, using the credentials from _server.env (via server_env.hpp)
// backup the service over ssh, using the credentials from server.env (via server_env.hpp)
// 1. run backup.sh on the server
// 2. create a backup file with format server-service-datetime.tgz
// 3. store it in the server's DROPSHELL_DIR/backups folder
@ -59,15 +58,17 @@ class server_service {
private:
std::string m_server_name;
std::string m_service_name;
ServiceInfo m_service_info;
std::unique_ptr<server_env> m_server_env;
std::string mRemote_service_path;
std::string mRemote_service_config_path;
std::string mRemote_service_template_path;
std::string mRemote_service_env_file;
// Helper methods
std::string construct_ssh_cmd() const;
std::string get_service_dir() const;
std::string get_env_path() const;
std::string get_script_dir() const;
bool check_service_dir_exists(const std::string& ssh_cmd) const;
bool check_remote_dir_exists(const std::string& ssh_cmd, const std::string& dir_path) const;
bool check_remote_file_exists(const std::string& ssh_cmd, const std::string& file_path) const;
bool execute_ssh_command(const std::string& command, const std::string& error_msg) const;
bool execute_local_command(const std::string& command, const std::string& error_msg) const;

93
src/services.cpp Normal file
View File

@ -0,0 +1,93 @@
#include "services.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp"
#include "templates.hpp"
#include <boost/filesystem.hpp>
#include <iostream>
namespace fs = boost::filesystem;
namespace dropshell {
std::vector<ServiceInfo> get_server_services_info(const std::string& server_name) {
std::vector<ServiceInfo> services;
std::string serverpath = get_local_config_servers_path();
if (serverpath.empty()) {
std::cerr << "Error: Server directory not found: " << serverpath << std::endl;
return services;
}
fs::path server_dir = fs::path(serverpath) / server_name;
if (!fs::exists(server_dir)) {
std::cerr << "Error: Server directory not found:" << server_dir.string() << std::endl;
return services;
}
for (const auto& entry : fs::directory_iterator(server_dir)) {
if (fs::is_directory(entry)) {
ServiceInfo service = get_service_info(server_name, entry.path().filename().string());
if (!service.template_name.empty()) {
services.push_back(service);
}
}
}
return services;
}
ServiceInfo get_service_info(const std::string &server_name, const std::string &service_name)
{
std::string service_dir = get_local_service_path(server_name, service_name);
if (service_dir.empty()) {
std::cerr << "Error: Service directory not found: " << service_dir << std::endl;
return ServiceInfo();
}
ServiceInfo service;
std::string local_service_env_path = get_local_service_env_path(server_name, service_name);
envmanager env(local_service_env_path);
if (!env.load()) {
std::cerr << "Error: service.env missing from " << local_service_env_path << std::endl;
return ServiceInfo();
}
service.template_name = env.get_variable("TEMPLATE");
if (service.template_name.empty()) {
std::cerr << "Error: TEMPLATE variable not defined in " << local_service_env_path << std::endl;
return ServiceInfo();
}
template_info tinfo;
template_manager tm;
if (!tm.get_template_info(service.template_name, tinfo)) {
std::cerr << "Error: Template '" << service.template_name << "' not found" << std::endl;
return ServiceInfo();
}
// find the template path
service.template_path = tinfo.path;
return service;
}
std::set<std::string> get_used_commands(const std::string &server_name, const std::string &service_name)
{
std::set<std::string> commands;
ServiceInfo service = get_service_info(server_name, service_name);
if (service.template_path.empty()) {
std::cerr << "Error: Service not found: " << service_name << std::endl;
return commands;
}
// iterate over all files in the template path, and add the command name to the set.
// commands are .sh files that don't begin with _
fs::path template_path = fs::path(service.template_path);
for (const auto& entry : fs::directory_iterator(template_path)) {
if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0))
commands.insert(entry.path().filename().string());
}
return commands;
}
} // namespace dropshell

24
src/services.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef SERVICES_HPP
#define SERVICES_HPP
#include <string>
#include <vector>
#include <set>
namespace dropshell {
struct ServiceInfo {
std::string path;
std::string service_name;
std::string template_name;
std::string template_path;
};
std::vector<ServiceInfo> get_server_services_info(const std::string& server_name);
ServiceInfo get_service_info(const std::string& server_name, const std::string& service_name);
std::set<std::string> get_used_commands(const std::string& server_name, const std::string& service_name);
} // namespace dropshell
#endif

View File

@ -1,10 +1,13 @@
#include "init_user_directory.hpp"
#include "templates.hpp"
#include "config.hpp"
#include "utils/directories.hpp"
#include <filesystem>
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iomanip>
namespace dropshell {
@ -20,7 +23,7 @@ bool template_manager::get_templates(std::vector<template_info>& templates) {
templates.clear();
// System templates directory
const std::string system_templates_dir = "/opt/dropshell/templates";
const std::string system_templates_dir = get_local_system_templates_path();
// User templates directory (from config)
std::string user_templates_dir;
if (!get_user_directory(user_templates_dir)) {
@ -77,4 +80,29 @@ bool template_manager::get_template_info(const std::string& name, template_info&
return false;
}
void list_templates() {
template_manager tm;
std::vector<template_info> templates;
if (!tm.get_templates(templates)) {
std::cerr << "Error: Failed to get templates" << std::endl;
return;
}
if (templates.empty()) {
std::cout << "No templates found." << std::endl;
return;
}
std::cout << "Available templates:" << std::endl;
std::cout << std::left << std::setw(20) << "Name" << "Path" << std::endl;
std::cout << std::string(60, '-') << std::endl;
for (const auto& t : templates) {
std::cout << std::left << std::setw(20) << t.name << t.path << std::endl;
}
}
} // namespace dropshell

160
src/utils/directories.cpp Normal file
View File

@ -0,0 +1,160 @@
#include "directories.hpp"
#include "config.hpp"
#include "server_env.hpp"
#include <iostream>
#include <string>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace dropshell {
std::string get_local_dropshell_config_path()
{
// Try ~/.config/dropshell/dropshell.conf
const char* home = std::getenv("HOME");
if (home) {
fs::path user_path = fs::path(home) / ".config" / "dropshell" / "dropshell.env";
return user_path.string();
}
std::cerr << "Warning: Couldn't determine user directory" << std::endl;
return std::string();
}
std::string get_local_system_templates_path()
{
return "/opt/dropshell/templates";
}
std::string get_local_config_path()
{
config *cfg = get_global_config();
std::string user_dir;
if (!cfg->get_local_config_directory(user_dir)) {
return std::string();
}
return user_dir;
}
std::string get_local_config_templates_path()
{
std::string config_path = get_local_config_path();
if (config_path.empty()) {
return std::string();
}
return config_path + "/templates";
}
std::string get_local_config_servers_path()
{
std::string config_path = get_local_config_path();
if (config_path.empty()) {
return std::string();
}
return config_path + "/servers";
}
std::string get_local_config_backups_path()
{
std::string config_path = get_local_config_path();
if (config_path.empty()) {
return std::string();
}
return config_path + "/backups";
}
std::string get_local_server_path(const std::string &server_name)
{
std::string config_path = get_local_config_path();
if (config_path.empty())
return std::string();
return config_path + "/servers/" + server_name;
}
std::string get_local_server_env_path(const std::string &server_name)
{
std::string serverpath = get_local_server_path(server_name);
if (serverpath.empty())
return std::string();
return (fs::path(serverpath) / "server.env").string();
}
std::string get_local_service_path(const std::string &server_name, const std::string &service_name)
{
std::string serverpath = get_local_server_path(server_name);
if (serverpath.empty())
return std::string();
return (fs::path(serverpath) / service_name).string();
}
std::string get_local_service_env_path(const std::string &server_name, const std::string &service_name)
{
std::string servicepath = get_local_service_path(server_name, service_name);
if (servicepath.empty())
return std::string();
return (fs::path(servicepath) / "service.env").string();
}
// ------------------------------------------------------------------------------------------
// remote paths
// DROPSHELL_DIR
// |-- service name
// |-- config
// |-- service.env
// |-- (user config files)
// |-- template
// |-- (script files)
// |-- backups
std::string get_remote_DROPSHELL_path(const std::string &server_name)
{
server_env env(server_name);
if (!env.is_valid())
return std::string();
return env.get_DROPSHELL_DIR();
}
std::string get_remote_service_path(const std::string &server_name, const std::string &service_name)
{
std::string dropshell_path = get_remote_DROPSHELL_path(server_name);
if (dropshell_path.empty())
return std::string();
return (fs::path(dropshell_path) / service_name).string();
}
std::string get_remote_service_config_path(const std::string &server_name, const std::string &service_name)
{
std::string service_path = get_remote_service_path(server_name, service_name);
if (service_path.empty())
return std::string();
return (fs::path(service_path) / "config").string();
}
std::string get_remote_service_template_path(const std::string &server_name, const std::string &service_name)
{
std::string service_path = get_remote_service_path(server_name, service_name);
if (service_path.empty())
return std::string();
return (fs::path(service_path) / "template").string();
}
std::string get_remote_service_backups_path(const std::string &server_name, const std::string &service_name)
{
std::string service_path = get_remote_service_path(server_name, service_name);
if (service_path.empty())
return std::string();
return (fs::path(service_path) / "backups").string();
}
std::string get_remote_service_env_file(const std::string &server_name, const std::string &service_name)
{
std::string service_path = get_remote_service_config_path(server_name, service_name);
if (service_path.empty())
return std::string();
return (fs::path(service_path) / "service.env").string();
}
} // namespace dropshell

40
src/utils/directories.hpp Normal file
View File

@ -0,0 +1,40 @@
#ifndef DIRECTORIES_HPP
#define DIRECTORIES_HPP
#include <string>
namespace dropshell {
// local paths - return empty string on failure
std::string get_local_dropshell_config_path();
std::string get_local_system_templates_path();
std::string get_local_config_path();
std::string get_local_config_templates_path();
std::string get_local_config_servers_path();
std::string get_local_config_backups_path();
std::string get_local_server_path(const std::string &server_name);
std::string get_local_server_env_path(const std::string &server_name);
std::string get_local_service_path(const std::string &server_name, const std::string &service_name);
std::string get_local_service_env_path(const std::string &server_name, const std::string &service_name);
// remote paths
// DROPSHELL_DIR
// |-- service name
// |-- config
// |-- service.env
// |-- (user config files)
// |-- template
// |-- (script files)
// |-- backups
std::string get_remote_DROPSHELL_path(const std::string &server_name);
std::string get_remote_service_path(const std::string &server_name, const std::string &service_name);
std::string get_remote_service_config_path(const std::string &server_name, const std::string &service_name);
std::string get_remote_service_template_path(const std::string &server_name, const std::string &service_name);
std::string get_remote_service_backups_path(const std::string &server_name, const std::string &service_name);
std::string get_remote_service_env_file(const std::string &server_name, const std::string &service_name);
} // namespace dropshell
#endif

135
src/utils/envmanager.cpp Normal file
View File

@ -0,0 +1,135 @@
#include "envmanager.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <regex>
#include <cstdlib> // For std::getenv
namespace dropshell {
envmanager::envmanager(std::string path) : m_path(path) {
}
envmanager::~envmanager() {
}
bool envmanager::load() {
std::ifstream file(m_path);
if (!file.is_open()) {
return false;
}
m_variables.clear();
std::string line;
while (std::getline(file, line)) {
line=trim(line);
// Skip empty lines and comments
if (line.empty() || line[0] == '#') {
continue;
}
size_t pos = line.find('=');
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
// trim whitespace from the key and value
m_variables[trim(key)] = trim(value);
}
}
file.close();
return true;
}
void envmanager::save() {
std::ofstream file(m_path);
if (!file.is_open()) {
return;
}
for (const auto& pair : m_variables) {
file << pair.first << "=" << pair.second << std::endl;
}
file.close();
}
std::string envmanager::get_variable(std::string key) const {
key = trim(key);
// Use case-insensitive comparison to find the key
for (const auto& pair : m_variables) {
if (pair.first == key) {
return pair.second;
}
}
return "";
}
void envmanager::get_all_variables(std::map<std::string, std::string>& variables) const {
variables = m_variables;
}
std::string envmanager::get_variable_substituted(std::string key) const {
std::string value = get_variable(key);
return expand_patterns(value);
}
void envmanager::get_all_variables_substituted(std::map<std::string, std::string>& variables) const {
variables.clear();
for (const auto& pair : m_variables) {
variables[pair.first] = expand_patterns(pair.second);
}
}
void envmanager::add_variables(std::map<std::string, std::string> variables) {
for (auto& pair : variables) {
set_variable(pair.first, pair.second);
}
}
void envmanager::set_variable(std::string key, std::string value) {
m_variables[trim(key)] = trim(value);
}
void envmanager::clear_variables() {
m_variables.clear();
}
std::string envmanager::trim(std::string str) const {
// Trim leading whitespace
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
// Trim trailing whitespace
str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), str.end());
return str;
}
std::string envmanager::expand_patterns(std::string str) const {
// Combined regex pattern for both ${var} and $var formats
std::regex var_pattern("\\$(?:\\{([^}]+)\\}|([a-zA-Z0-9_]+))");
std::string result = str;
std::smatch match;
while (std::regex_search(result, match, var_pattern)) {
// match[1] will contain capture from ${var} format
// match[2] will contain capture from $var format
std::string var_name = match[1].matched ? match[1].str() : match[2].str();
// Get value from system environment variables
const char* env_value = std::getenv(var_name.c_str());
std::string value = env_value ? env_value : "";
result = result.replace(match.position(), match.length(), value);
}
return result;
}
} // namespace dropshell

49
src/utils/envmanager.hpp Normal file
View File

@ -0,0 +1,49 @@
#ifndef ENV_MANAGER_HPP
#define ENV_MANAGER_HPP
#include <string>
#include <map>
namespace dropshell {
// envmanager is a class that manages the environment files for the application.
// it is responsible for loading the environment files, and providing a class to access the variables.
// it can also save the environment files.
class envmanager {
public:
envmanager(std::string path);
~envmanager();
// load all variables from the environment file
bool load();
// save all variables to the environment file
void save();
// get variables from the environment files. Trim whitespace from the values.
// keys are case-sensitive.
std::string get_variable(std::string key) const;
void get_all_variables(std::map<std::string, std::string>& variables) const;
// get variables, but replace patterns ${var} and $var with the actual environment variable in the returned string.
// trim whitespace from the values.
std::string get_variable_substituted(std::string key) const;
void get_all_variables_substituted(std::map<std::string, std::string>& variables) const;
// add variables to the environment files.
// trim whitespace from the values.
void add_variables(std::map<std::string, std::string> variables);
void set_variable(std::string key, std::string value);
void clear_variables();
private:
std::string trim(std::string str) const;
std::string expand_patterns(std::string str) const;
private:
std::string m_path;
std::map<std::string, std::string> m_variables;
};
} // namespace dropshell
#endif

View File

@ -1,16 +0,0 @@
# This file contains environment variables specific to the server where the application will be deployed.
# Copy this file to .env and modify the values according to your server's configuration.
# Do not commit the actual .env file to version control as it may contain sensitive information.
# Application settings
CONTAINER_PORT=8181
HOST_PORT=80
# Deployment settings
LOCAL_DATA_FOLDER="${HOME}/.sk"
CONTAINER_NAME="squashkiwi"
# Image settings
IMAGE_REGISTRY="gitea.jde.nz"
IMAGE_REPO="squashkiwi/squashkiwi"
IMAGE_TAG="latest"

View File

@ -0,0 +1,15 @@
# Service settings
TEMPLATE=squashkiwi
# Application settings
CONTAINER_PORT=8181
HOST_PORT=80
# Deployment settings
LOCAL_DATA_FOLDER="${HOME}/.sk"
CONTAINER_NAME="squashkiwi"
# Image settings
IMAGE_REGISTRY="gitea.jde.nz"
IMAGE_REPO="squashkiwi/squashkiwi"
IMAGE_TAG="latest"